Metadata-Version: 2.4
Name: shrine
Version: 0.3.0
Summary: SHRINE — Simulation of Hydrology, Reservoirs, and Integrated Network Engine
Author: Jason Lillywhite
License-Expression: MIT
Project-URL: Homepage, https://github.com/jlillywh/SHRINE
Project-URL: Documentation, https://jlillywh.github.io/SHRINE/
Project-URL: Repository, https://github.com/jlillywh/SHRINE
Project-URL: Issues, https://github.com/jlillywh/SHRINE/issues
Project-URL: Discussions, https://github.com/jlillywh/SHRINE/discussions
Project-URL: Changelog, https://github.com/jlillywh/SHRINE/blob/master/CHANGELOG.md
Project-URL: Contributing, https://github.com/jlillywh/SHRINE/blob/master/CONTRIBUTING.md
Project-URL: CodeOfConduct, https://github.com/jlillywh/SHRINE/blob/master/CODE_OF_CONDUCT.md
Project-URL: Governance, https://github.com/jlillywh/SHRINE/blob/master/GOVERNANCE.md
Project-URL: Security, https://github.com/jlillywh/SHRINE/blob/master/SECURITY.md
Keywords: hydrology,water-resources,simulation,reservoir,watershed
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: networkx>=3.0
Requires-Dist: numpy
Requires-Dist: pandas
Requires-Dist: openpyxl>=3.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: pint>=0.23
Requires-Dist: iapws>=1.2
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: pre-commit>=3.0; extra == "dev"
Requires-Dist: mypy>=1.8; extra == "dev"
Requires-Dist: types-PyYAML; extra == "dev"
Requires-Dist: pandas-stubs; extra == "dev"
Requires-Dist: types-networkx; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"
Requires-Dist: cookiecutter>=2.5; extra == "dev"
Provides-Extra: viz
Requires-Dist: matplotlib>=3.7; extra == "viz"
Provides-Extra: hydrology
Requires-Dist: hydrofunctions>=0.2.4; extra == "hydrology"
Provides-Extra: climate
Requires-Dist: pydaymet>=0.19; extra == "climate"
Requires-Dist: requests>=2.28; extra == "climate"
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
Requires-Dist: mkdocstrings[python]>=0.27; extra == "docs"
Dynamic: license-file

[![PyPI version](https://img.shields.io/pypi/v/shrine?logo=pypi&logoColor=white&label=version&v=0.3.0)](https://pypi.org/project/shrine/)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![codecov](https://codecov.io/gh/jlillywh/SHRINE/graph/badge.svg?branch=master)](https://codecov.io/gh/jlillywh/SHRINE)

# SHRINE

**S**imulation of **H**ydrology, **R**eservoirs, and **I**ntegrated **N**etwork **E**nvironments.

Open-source integrated water-resources modeling library (Python). SHRINE combines legacy domain modules (hydrology, storage, flow networks) with **`shrine.simulation`** for calendar-driven runs, mass balance, scenario files, and structured outputs.

Naming details: [docs/project-name.md](docs/project-name.md).

## Simulation framework (`shrine.simulation`)

The framework is the **only supported path** for new model runs. Import from the package root:

```python
import shrine.simulation as sim
# or
from shrine.simulation import Model, RunController, Clock, WatershedElement
```

**Package versions:** `shrine.__version__` (distribution) and `shrine.simulation.__api_version__` (stable simulation API surface, currently **1.1**). Stability and deprecation rules: [docs/api-stability.md](docs/api-stability.md). Release history: [CHANGELOG.md](CHANGELOG.md); SemVer and maintainer checklist: [docs/releases.md](docs/releases.md). Submodules outside `__all__` are internal unless noted in [extending-elements.md](docs/extending-elements.md).

### Public API

| Category | Symbols |
|----------|---------|
| **Run** | `Model`, `RegisteredElement`, `RunController`, `RunResult`, `RunSession`, `StepResult`, `ElementScheduler` |
| **Time** | `Clock`, `RunContext`, `TimestepContext` |
| **Elements** | `Simulatable`, `WatershedElement`, `CatchmentElement`, `ReservoirElement`, `ReservoirNetworkElement`, `ClimateRecorderElement`, `StorageLike` |
| **Plugins** | `list_element_plugins`, `load_element_plugin`, `create_element_from_plugin` (`shrine.elements` entry points) |
| **Inputs** | `InputManager`, `InputProvider`, `ConstantInput`, `MonthlyLookupInput`, `StochasticInput` |
| **Flow / balance** | `FlowSolver`, `NetworkXFlowSolver`, `FlowSolveResult`, `MassBalanceCheck`, `MassBalanceReport`, `MassBalanceTerm` |
| **Outputs / scenarios** | `Recorder`, `ScenarioConfig`, `load_scenario_file`, `run_scenario`, `run_scenarios`, `load_and_run` |
| **Metadata / RNG** | `build_run_metadata`, `enrich_run_metadata`, `RunTimer`, `make_rng` |
| **Deprecation** | `warn_api_deprecated` |
| **Errors** | `SimulationError`, `SimulationPhase` |

Capabilities:

- **`Model`** — register elements (watersheds, reservoirs, custom types) with a shared `Clock`
- **`RunController`** — validate → initialize → timestep loop → finalize
- **`InputManager`** — constant, monthly, and stochastic inputs bound by name
- **`Recorder`** — wide `pandas` DataFrame outputs per run
- **Scenarios** — YAML/JSON clock, inputs, and parameter overrides
- **Flow solve** — NetworkX max-flow on graphs owned by `Watershed` adapters
- **Mass balance** — per-timestep verification with `SimulationError` diagnostics

Requirements and phased delivery: [docs/simulation-framework-requirements.md](docs/simulation-framework-requirements.md). Layer diagram: [docs/architecture.md](docs/architecture.md).

## Hydrology (`src/hydrology/`)

Legacy and evolving **rainfall–runoff** and **watershed network** code lives under `src/hydrology/`. New runs should use **`shrine.simulation`** adapters (`CatchmentElement`, `WatershedElement`) with a `RunoffMethod` or custom `RunoffModel` on each catchment. Contracts and adapters: [docs/hydrology-contracts.md](docs/hydrology-contracts.md). Muskingum routing, snow models, and future kinematic wave work: [docs/river-reservoir-roadmap.md](docs/river-reservoir-roadmap.md).

### Rainfall–runoff models

| Model | `RunoffMethod` | Module | Framework-ready | Notes |
|-------|----------------|--------|-----------------|-------|
| **Rational** (simple loss) | `SIMPLE` | `catchment.Rational` | Yes | Default; fast sanity checks and reference scenarios |
| **AWBM** (Australian Water Balance Model) | `AWBM` | `awbm.Awbm` | Yes | Boughton (2004); verified via fixture + mass balance — [docs/awbm-verification.md](docs/awbm-verification.md) |
| **Snow-17** (temperature-index snow) | `SNOW17` | `snow17.Snow17RunoffModel` | Yes | Independent reference twin + fixture — [docs/snow17-verification.md](docs/snow17-verification.md) |
| **AWBM + Snow-17** | `AWBM_SNOW17` | `snow17.AWBM_Snow17_RunoffModel` | Yes | Snowpack → AWBM soil; [docs/awbm-snow17.md](docs/awbm-snow17.md) |
| **GR4J** | `GR4J` | `gr4j.Gr4jRunoffModel` | Yes | airGR `frun_gr4j` port — [docs/gr4j-cemaneige-verification.md](docs/gr4j-cemaneige-verification.md) |
| **GR4J + CemaNeige** | `GR4J_CEMANNEIGE` | `gr4j_cemaneige.GR4J_CemaneigeRunoffModel` | Yes | INRAE snow + runoff — [docs/gr4j-cemaneige.md](docs/gr4j-cemaneige.md) |
| **Sacramento** soil moisture accounting | — | `sacramento.Sacramento` | No | Legacy class; deferred in favor of GR4J path |

Use with the framework:

```python
from hydrology.enums import RunoffMethod
from shrine.simulation import CatchmentElement, Model, RunController

model.register_catchment(
    "c1",
    CatchmentElement(
        element_id="c1",
        area=1_000_000.0,
        runoff_method=RunoffMethod.AWBM,
        temperature_key="temperature",  # Snow-17 / AWBM+Snow-17
    ),
)
```

Custom physics: implement `RunoffModel` in `hydrology/protocols.py` and pass an instance to `Catchment(..., runoff_method=model)`.

### Networks and routing

| Component | Module | Role |
|-----------|--------|------|
| **Catchment** | `catchment` | Single hillslope / subcatchment runoff (`area` × depth rate → volume) |
| **Watershed** | `watershed` | NetworkX graph of catchments, junctions, sink; capacity updates + max-flow or Muskingum routing via `WatershedElement` |
| **Muskingum reach** | `muskingum`, `reach_routing`, `routed_flow` | Lagged channel routing with internal substeps (`ReachRoutingConfig`); `Watershed.set_muskingum_reach` |
| **Graph payloads** | `graph_nodes` | Typed `CatchmentNode`, `JunctionNode`, `SinkNode`, `SourceNode` on graph nodes |
| **Junction** | `junction` | Legacy junction helpers |

GML test data: `src/hydrology/test_data/`. Examples: [examples/catchment_run.py](examples/catchment_run.py), [examples/watershed_run.py](examples/watershed_run.py).

**Muskingum routing (R1.5.3)** — optional lagged reaches with an internal routing time step inside each daily run step:

```python
from hydrology import Watershed
from shrine.simulation import WatershedElement

ws = Watershed()
ws.add_junction("J1", "sink")
ws.link_catchment("C1", "J1")
ws.set_muskingum_reach("J1", "sink", K=2.0, X=0.2, n_substeps=24)

element = WatershedElement(ws, element_id="ws1", reach_routing="muskingum")
```

**Prerun flow slots (R1.5.4)** — warm Muskingum state before the first recorded step (RiverWare *Initialize Flow Slots for Routing*):

```python
from hydrology import PrerunConfig

# Manual warm start on the watershed graph
ws.update_capacity("C1", catchment_supply)
ws.prerun_routing(inflow_rates={"C1": catchment_supply})  # n_timesteps from max K

# Or via WatershedElement (uses first timestep supplies when inflow_rate is omitted)
element = WatershedElement(
    ws,
    reach_routing="muskingum",
    prerun=PrerunConfig(n_timesteps=12),
)
```

Default `reach_routing="instant"` keeps zero-lag max-flow. Tests: `tests/hydrology/test_muskingum.py`, `tests/hydrology/test_routed_flow.py`, `tests/hydrology/test_prerun_routing.py`, `tests/hydrology/test_inflow_hydrograph.py`. See [reservoir-roadmap §8](docs/reservoir-roadmap.md#8-temporal-discretization-riverware-alignment).

**Intra-step inflow hydrographs (R1.5.5)** — non-uniform inflow within a daily run step for pool and reach substeps:

```python
from water_manage.reservoir import SubstepConfig, ReservoirModel, default_test_curve, default_test_spillway

# Reservoir pool substeps — storm front-loaded into the day
model = ReservoirModel(
    curve=default_test_curve(),
    storage=174.0,
    spillway=default_test_spillway(),
    hydraulic_substeps=SubstepConfig(n_substeps=24, inflow_pattern="front_loaded"),
)

# Muskingum reach — triangular inflow within the day
ws.set_muskingum_reach(
    "J1",
    "sink",
    K=2.0,
    X=0.2,
    n_substeps=24,
    inflow_pattern="triangular",
)
```

Patterns: ``uniform`` (default), ``front_loaded``, ``back_loaded``, ``triangular``, or ``custom`` with explicit ``inflow_weights``.

More hydrology (kinematic wave, etc.) is tracked on the [river–reservoir roadmap](docs/river-reservoir-roadmap.md). **Reservoir operations** (level-pool routing, outlet rating curves, rule curves, hydropower) have a dedicated checklist: [docs/reservoir-roadmap.md](docs/reservoir-roadmap.md). When you add a model, extend the table above and link any verification doc or test module.

## Reservoir operations (`src/water_manage/reservoir/`)

Composable **level-pool** reservoir model for `ReservoirElement` (roadmap phases R0–R5). Legacy monolithic `Reservoir` remains in `reservoir/legacy.py` for backward compatibility.

| Component | Module | Status |
|-----------|--------|--------|
| **StageStorageCurve** | `stage_storage` | Elevation ↔ storage ↔ area lookup |
| **LevelPoolRouter** | `level_pool` | Daily mass balance `S' = S + I − Q` |
| **ReservoirModel** | `model` | `StorageElement` backend; optional outlet + spillway |
| **ReservoirNetwork** | `reservoir_network` | Coupled pool → reach → pool networks (`ReservoirNetworkElement`) |
| **OutletWorks** | `outlet_works` | Head–discharge rating; intake invert; gate opening |
| **Spillway** | `spillway` | Broad-crested weir; sharp/ogee stubs |
| **Hydraulic substeps** | `hydraulic_substeps` | Optional intra-day pool updates with configurable inflow hydrograph; see [reservoir-roadmap §8](docs/reservoir-roadmap.md#8-temporal-discretization-riverware-alignment) |
| **RuleCurve / OperationalRules** | `rule_curve`, `operational_rules` | Seasonal target pool; hold-target release |
| **Hydropower** | `hydropower` | Fixed-η turbine plant |

Cross-check vs RiverWare / HEC-ResSim: [docs/reservoir-riverware-verification.md](docs/reservoir-riverware-verification.md) (R5.4). Tutorial: [examples/reservoir_rule_curve.py](examples/reservoir_rule_curve.py) (R5.3).

Reference scenario: `scenarios/reference/simple_level_pool_reservoir.yaml` (REF-RES-LP).

**Multi-reservoir network (R5.1)** — instant reaches between pools (zero lag within a run step). **R5.2** adds Muskingum lag on inter-reservoir edges. Tests: `tests/water_manage/test_reservoir_network.py`, `tests/simulation/test_reservoir_network_adapter.py`.

```python
from hydrology.reach_routing import PrerunConfig, ReachRoutingConfig
from shrine.simulation import (
    Clock,
    ConstantInput,
    InputManager,
    Model,
    ReservoirNetworkElement,
    RunController,
)
from water_manage.reservoir import ReservoirModel, ReservoirNetwork, default_test_curve

network = ReservoirNetwork()
network.add_reservoir("upper", ReservoirModel(curve=default_test_curve(), storage=500.0))
network.add_reservoir("lower", ReservoirModel(curve=default_test_curve(), storage=50.0))
network.link_muskingum_reach(
    "upper",
    "lower",
    K=2.0,
    X=0.2,
    routing=ReachRoutingConfig(n_substeps=24),
)

model = Model(clock=Clock("1/1/2019", "1/3/2019"))
model.register_reservoir_network(
    "system",
    ReservoirNetworkElement(network, element_id="system", prerun=PrerunConfig(n_timesteps=12)),
)

inputs = InputManager()
inputs.bind("upper.inflow", ConstantInput(8.0))
inputs.bind("upper.release", ConstantInput(8.0))
inputs.bind("lower.release", ConstantInput(0.0))

result = RunController(model, input_manager=inputs).run()
assert result.outputs["system.lower.routed_inflow"].iloc[0] > 0.0
```

Instant reaches (R5.1 only) use ``network.link_reach("upper", "lower")`` instead of ``link_muskingum_reach``.

```python
from water_manage.reservoir import (
    OutletRatingCurve,
    OutletWorks,
    ReservoirModel,
    default_test_curve,
    default_test_spillway,
)
from shrine.simulation import ReservoirElement, Model, RunController, ConstantInput, InputManager

backend = ReservoirModel(
    curve=default_test_curve(),
    storage=50.0,
    outlet=OutletWorks(
        invert_elevation=3.75,
        rating=OutletRatingCurve.power_law(coefficient=3.2, exponent=1.5),
    ),
    spillway=default_test_spillway(),
)
model = Model()
model.register("res1", ReservoirElement(backend, element_id="res1"))
inputs = InputManager()
inputs.bind("inflow", ConstantInput(10.0))
inputs.bind("release", ConstantInput(5.0))
RunController(model, input_manager=inputs).run()
```

## Project layout

| Area | Description |
|------|-------------|
| `src/shrine/simulation/` | Framework: clock, model, run controller, inputs, recorder, scenarios |
| `src/hydrology/` | Catchments, watersheds, networks |
| `src/water_manage/` | Storage, flow networks, **composable reservoir model** (`reservoir/`) |
| `src/inputs/`, `src/results/` | Tables, time series, `TimeHistory`, charts |
| `examples/` | Runnable demos (climate, watershed, scenarios, stepping) |
| `shrine-element-cookiecutter/` | Cookiecutter template for third-party `shrine.elements` plugins ([guide](docs/cookiecutter-element.md)) |
| `tests/simulation/` | Framework unit and acceptance tests |
| `docs/` | Guides (see below) |

Library code is under `src/`; run `pip install -e ".[dev]"` before tests or scripts (see [docs/testing.md](docs/testing.md)).

## Prerequisites

- Python **3.10+**
- WSL/Linux or Windows with WSL recommended for `./scripts/run_tests.sh`

## Install

**PyPI:** `pip install shrine` — see [docs/install.md](docs/install.md) (extras, PEP 668, wheel vs clone). **Development:** clone + editable install below.

### From PyPI

```bash
pip install shrine
pip install "shrine[dev,viz,hydrology]"
```

The PyPI wheel includes Python packages and `examples/`; **clone the repo** for bundled `scenarios/` and `./scripts/run_tests.sh`.

### From source *(development)*

```bash
git clone https://github.com/jlillywh/SHRINE.git
cd SHRINE
bash scripts/bootstrap_venv.sh    # .venv + pip install -e ".[dev]"
```

Contributors (tests + plotting + NWIS demo):

```bash
.venv/bin/python3 -m pip install -e ".[dev,viz,hydrology]"
```

On Ubuntu/WSL, use `.venv/bin/python3` and `.venv/bin/pip` — system `pip install` is blocked (PEP 668). See [docs/install.md](docs/install.md).

### Optional dependency extras

Defined in `pyproject.toml` under `[project.optional-dependencies]`. Same extra names for source and PyPI.

| Extra | Purpose | Source | PyPI |
|-------|---------|--------|------|
| *(none)* | Core runtime | `pip install -e .` | `pip install shrine` |
| `dev` | pytest, mypy, ruff, pre-commit | `pip install -e ".[dev]"` | `pip install "shrine[dev]"` |
| `docs` | MkDocs site | `pip install -e ".[docs]"` | `pip install "shrine[docs]"` |
| `viz` | matplotlib | `pip install -e ".[viz]"` | `pip install "shrine[viz]"` |
| `hydrology` | NWIS demo (`examples/nwis_streamflow.py`) | `pip install -e ".[hydrology]"` | `pip install "shrine[hydrology]"` |

**Recommended for contributors:**

```bash
pip install -e ".[dev,viz,hydrology]"
```

**Framework-only CI** (matches `./scripts/run_tests.sh`):

```bash
pip install -e ".[dev]"
```

## Run tests

```bash
./scripts/run_tests.sh
```

Or manually:

```bash
pytest tests/ -v
pytest tests/simulation --cov=shrine.simulation --cov-report=term-missing
```

See [docs/testing.md](docs/testing.md) for layout and troubleshooting (WSL sync, venv, etc.).

## Secrets and credentials

Do **not** commit API keys or `.env` files. Use `GOOGLE_MAPS_API_KEY` for the optional Maps demo, or a local gitignored `data_external/apikey.txt` (see `data_external/apikey.txt.example`). Full guidance: **[docs/secrets-and-repo-hygiene.md](docs/secrets-and-repo-hygiene.md)**. Optional local hook (with venv activated): `pip install -e ".[dev]" && pre-commit install` — gitleaks on commit; CI runs the same scan on push/PR.

## Examples

From the repo root with the venv activated:

```bash
# Climate inputs via framework (no Excel)
python examples/climate_loop.py

# Two catchments → junction → flow solve
python examples/watershed_run.py

# Single catchment, rational runoff (no network)
python examples/catchment_run.py

# Rule-curve reservoir tutorial (R5.3)
python examples/reservoir_rule_curve.py

# Scenario file (JSON/YAML)
python examples/run_from_scenario.py scenarios/baseline_watershed.json

# Tutorial: watershed + monthly scenario + plot (roadmap 3.3)
python examples/tutorial_watershed.py --no-show --output tutorial_plot.png

# Single-timestep debugging
python examples/step_debug.py

# Minimal custom element
python examples/custom_element.py

# USGS NWIS fetch (optional: pip install -e ".[hydrology]")
python examples/nwis_streamflow.py
```

Bundled scenarios: `scenarios/baseline_watershed.json`, `scenarios/wet_year.yaml`.

## Documentation

**Online docs:** [https://jlillywh.github.io/SHRINE/](https://jlillywh.github.io/SHRINE/) (GitHub Pages — enable *Settings → Pages → GitHub Actions* after the first `Docs` workflow run on `master`).

Build locally:

```bash
pip install -e ".[docs]"
mkdocs serve          # http://127.0.0.1:8000
# or: ./scripts/build_docs.sh
```

| Guide | Topic |
|-------|--------|
| [Architecture](https://jlillywh.github.io/SHRINE/architecture/) | **Framework vs domain vs adapters** (diagrams) |
| [Comparison with other tools](https://jlillywh.github.io/SHRINE/comparison/) | SHRINE vs PySWMM, WEAP, ResSim, Spotpy, … (honest scope) |
| [docs/project-name.md](docs/project-name.md) | **SHRINE** naming and acronym |
| [docs/modernization-roadmap.md](docs/modernization-roadmap.md) | Strategic checklist: pythonic OOP, OSS excellence |
| [docs/api-stability.md](docs/api-stability.md) | SemVer, deprecation cycle, public API policy |
| [CHANGELOG.md](CHANGELOG.md) | Release history ([Keep a Changelog](https://keepachangelog.com/)) |
| [GOVERNANCE.md](GOVERNANCE.md) | Maintainer, release manager, lazy consensus |
| [SECURITY.md](SECURITY.md) | Vulnerability reporting and supported versions |
| [docs/releases.md](docs/releases.md) | Versioning policy and maintainer release checklist |
| [docs/simulation-framework-requirements.md](docs/simulation-framework-requirements.md) | Architecture decisions and requirements |
| [docs/extending-elements.md](docs/extending-elements.md) | Adding new `Simulatable` elements |
| [docs/cookiecutter-element.md](docs/cookiecutter-element.md) | Cookiecutter template for plugin packages |
| [docs/scenarios.md](docs/scenarios.md) | Scenario YAML/JSON |
| [docs/step-debugging.md](docs/step-debugging.md) | `RunController.step()` API |
| [docs/results-recording.md](docs/results-recording.md) | `Recorder` and `TimeHistory` |
| [docs/install.md](docs/install.md) | **Install** — source vs PyPI, extras, PEP 668, wheel vs clone |
| [docs/testing.md](docs/testing.md) | Test suite and CI-style local runs |
| [docs/hydrology-contracts.md](docs/hydrology-contracts.md) | RunoffModel, catchments, graph node contracts |
| [docs/awbm-verification.md](docs/awbm-verification.md) | AWBM verification (fixture, mass balance, eWater SRG) |
| [docs/snow17-verification.md](docs/snow17-verification.md) | Snow-17 verification (reference twin, GitHub NWS ports) |
| [docs/awbm-snow17.md](docs/awbm-snow17.md) | Combined AWBM + Snow-17 usage and units |
| [docs/snow-dominated-verification.md](docs/snow-dominated-verification.md) | Seasonal snow-dominated regression case |
| [docs/river-reservoir-roadmap.md](docs/river-reservoir-roadmap.md) | Snow, Muskingum routing, reservoir networks — hydrology roadmap |
| [docs/reservoir-roadmap.md](docs/reservoir-roadmap.md) | Level-pool reservoir, outlets, rules, hydropower (R0–R5) |
| [docs/secrets-and-repo-hygiene.md](docs/secrets-and-repo-hygiene.md) | API keys, `.env`, history purge if a secret was committed |

## Quick API sketch

```python
from shrine.simulation import (
    Clock,
    ConstantInput,
    InputManager,
    Model,
    RunController,
    WatershedElement,
)
from hydrology.watershed import Watershed

ws = Watershed()
ws.add_junction("J1", "sink")
ws.link_catchment("C1", "J1")

model = Model(clock=Clock("1/1/2019", "1/15/2019"))
model.register_watershed("basin", WatershedElement(ws, element_id="basin"))

inputs = InputManager()
inputs.bind("precipitation", ConstantInput(10.0))
inputs.bind("evaporation", ConstantInput(1.0))

result = RunController(model, input_manager=inputs, seed=42).run()
print(result.outputs.head())
```

Legacy scripts (e.g. `global_attributes/test_model.py`) remain for reference; prefer `examples/climate_loop.py` and the framework APIs for new work.

## Community

| Need | Where |
|------|-------|
| **Questions**, ideas, show-and-tell | [GitHub Discussions](https://github.com/jlillywh/SHRINE/discussions) |
| **Bugs** and **feature requests** | [Issues](https://github.com/jlillywh/SHRINE/issues/new/choose) |
| **Good first issues** | [Issues labeled `good first issue`](https://github.com/jlillywh/SHRINE/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) |
| **Help wanted** | [Issues labeled `help wanted`](https://github.com/jlillywh/SHRINE/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) |
| **Security** vulnerabilities | [SECURITY.md](SECURITY.md) (private reporting — do not open a public issue) |

When you [start a discussion](https://github.com/jlillywh/SHRINE/discussions/new/choose), choose a category:

- **Q&A** — how-to questions, troubleshooting, API usage
- **Ideas** — feature proposals before they become issues
- **Show and tell** — scenarios, plugins, teaching examples, integrations

**Announcements** are for maintainer updates (for example the welcome thread). Please follow the [Code of Conduct](CODE_OF_CONDUCT.md).

## Contributing

We welcome pull requests. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, tests, and PR workflow. For questions, use [Discussions](https://github.com/jlillywh/SHRINE/discussions) (table above).

## License

MIT License — see [LICENSE](LICENSE).
