Metadata-Version: 2.4
Name: cnkit
Version: 1.0.0
Summary: Curve number hydrology with Earth Observation: NRCS runoff relations, NLCD/HSG lookup, event separation, asymptotic CN, and antecedent moisture.
Project-URL: Homepage, https://github.com/skp703/cnkit
Project-URL: Repository, https://github.com/skp703/cnkit
Project-URL: Issues, https://github.com/skp703/cnkit/issues
Project-URL: Changelog, https://github.com/skp703/cnkit/blob/main/CHANGELOG.md
Project-URL: Documentation, https://github.com/skp703/cnkit#api-overview
Author: John J. Ramirez-Avila
Author-email: Saurav Kumar <kumar.saurav@gmail.com>
Maintainer: John J. Ramirez-Avila
Maintainer-email: Saurav Kumar <kumar.saurav@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: NRCS,SCS,TR-55,curve number,hydrology,remote sensing,runoff
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Education
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Scientific/Engineering :: Hydrology
Requires-Python: >=3.9
Requires-Dist: numpy>=1.21
Requires-Dist: pandas>=1.3
Requires-Dist: scipy>=1.7
Provides-Extra: data
Requires-Dist: requests>=2.25; extra == 'data'
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: flake8>=7.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: twine>=6.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: pdoc>=14.0; extra == 'docs'
Provides-Extra: test
Requires-Dist: pytest-cov>=4.0; extra == 'test'
Requires-Dist: pytest>=7.0; extra == 'test'
Description-Content-Type: text/markdown

# cnkit

[![CI](https://github.com/skp703/cnkit/actions/workflows/ci.yml/badge.svg)](https://github.com/skp703/cnkit/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/cnkit.svg)](https://pypi.org/project/cnkit/)
[![Python](https://img.shields.io/pypi/pyversions/cnkit.svg)](https://pypi.org/project/cnkit/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Curve number hydrology with Earth Observation. `cnkit` implements the NRCS
curve number method as it is actually used when the inputs come from satellite
data: the runoff relations and the initial abstraction ratio conversions, the
NLCD land cover by hydrologic soil group lookup with its assumptions stated
rather than buried, storm event and baseflow separation, the Hawkins asymptotic
curve number and its competing estimators, and the antecedent moisture
conventions.

The argument the library is built around:

> Earth Observation has moved the curve number from an unverifiable lookup to a
> measurable, spatially and temporally resolved, uncertainty-quantified index.
> In doing so it has made visible the fact that the remaining error is dominated
> by the 1954 equation, not by the inputs.

That is why the API returns disagreements instead of hiding them. Where a
quantity has more than one defensible definition, `cnkit` computes all of them
and hands you the spread: `composite_runoff` returns three runoff depths,
`compare_estimators` returns four curve numbers, `compare_conventions` returns
the antecedent classes that the 1972 rainfall rule and the satellite soil
moisture percentile assign to the same storm.

## Install

```bash
pip install cnkit
```

The core science needs only numpy, pandas and scipy. The `cnkit.data` fetchers
(USGS NWIS, USDA Soil Data Access) need an HTTP client, which is an extra:

```bash
pip install "cnkit[data]"
```

From a clone, for development:

```bash
git clone https://github.com/skp703/cnkit.git
cd cnkit
pip install -e ".[dev,data]"
```

Python 3.9 or newer.

## Quick start

```python
>>> from cnkit import runoff, composite_runoff
>>> round(float(runoff(3.0, 75)), 4)      # 3 inches of rain on CN 75
0.9608
>>> # The weighting trap from Module 3.
>>> # 60 percent impervious at CN 98, 40 percent woods at CN 55, 1 inch storm.
>>> qd, qcn, qs = composite_runoff(1.0, [98, 55], [0.6, 0.4])
>>> [round(x, 4) for x in (qd, qcn, qs)]
[0.4745, 0.0949, 0.0277]
```

Those three numbers are all "the runoff from this watershed". The first
computes runoff per subarea and then averages. The second averages the curve
number and then computes runoff. The third averages the retention S and then
computes runoff. They differ by a factor of five and seventeen.

The reason is that CN 55 has Ia = 1.64 inches, so under the distributed
calculation the wooded 40 percent contributes exactly nothing to a 1 inch
storm, while the lumped composite CN of 80.8 has Ia = 0.48 inches and quietly
assumes the entire watershed is partly absorbing. The gap narrows as the storm
grows, to roughly two-fold at 2 inches, which is why nobody notices the problem
when they sanity-check their work against a 100-year event. Which of the three
goes in the report is a real engineering decision, and almost nobody makes it
consciously.

That example is a doctest. It runs in CI on every push.

## API overview

| Module | What it does |
| --- | --- |
| `cnkit.core` | S, Q, initial abstraction, back-calculation of CN from observed P and Q, the lambda 0.05 against lambda 0.20 conversions, the ARC 1 and ARC 3 adjustments, the Williams slope adjustment, and both composite CN weighting conventions. |
| `cnkit.lookup` | NLCD Level II crossed with hydrologic soil group to CN, with the hydrologic condition assumption made explicit per class, plus the condition spread and area-weighted composites. |
| `cnkit.events` | Storm event separation from a rainfall series, baseflow separation by local minimum and by the Lyne and Hollick filter, and the rainfall runoff event table the asymptotic fit consumes. |
| `cnkit.asymptotic` | The Hawkins asymptotic CN, the standard, complacent and violent behaviour classification, and the four competing estimators that do not agree with each other. |
| `cnkit.antecedent` | The 1972 five-day antecedent rainfall AMC class set against the satellite soil moisture percentile, with a day-of-year climatology and a disagreement table. |

A sixth module, `cnkit.data`, holds no-authentication fetchers for USGS NWIS
and USDA Soil Data Access plus the workshop watershed registry. It is separated
from the science because it is the only part that touches the network, and it
is the only part that needs the `data` extra.

### Units and input validation

- Rainfall, runoff and storage depths are in inches unless a function says
  otherwise. Drainage area arguments state their units in their names.
- Curve numbers are dimensionless. `S_from_CN` deliberately clips values to
  `[1, 100]`; composite calculations reject values outside `(0, 100]` because
  silently weighting an invalid CN would contaminate the entire result.
- Initial abstraction ratio `lam` must be a finite scalar between 0 and 1.
- Missing numerical inputs propagate as `NaN` where a numerical result can be
  returned honestly. Structurally invalid inputs, such as negative rainfall,
  zero total area, incomplete antecedent windows or an unknown model name,
  raise `ValueError`.
- Functions accept NumPy arrays where vector operation is meaningful. Event
  and climatology functions require pandas Series with a DatetimeIndex.

## What this library will not do

`cnkit` computes **runoff volume**. It does not compute peak discharge, time to
peak, time of concentration, or hydrographs. The curve number method is a
volume method. TR-55 chapter 4, unit hydrograph convolution and every routing
step downstream of that are out of scope, and pairing a volume from this
library with a peak from somewhere else is your decision to document.

Two range limits are worth stating before anyone runs a design with these
numbers:

- **CN below 30.** The NRCS tables are not defined below CN 30 and the method
  is not reliable there. Very low curve numbers imply retention values so large
  that the initial abstraction alone exceeds most design storms, and the result
  is a runoff depth of zero that is an artefact of the equation rather than a
  hydrologic finding.
- **Runoff below 0.5 inches.** Below roughly half an inch of computed runoff the
  method's error is the same size as its answer. Back-calculated curve numbers
  from small events are the main source of the apparent scatter in event data,
  which is exactly why the asymptotic fit exists.

Neither limit is enforced by the code. The functions will happily return a
number outside them. Checking that you are inside them is the analyst's job.

## Testing

The suite has five layers, and they fail for different reasons on purpose.

1. **Unit and validation tests on the public API.** `tests/test_cnkit.py` and
   `tests/test_public_api_validation.py` cover textbook S and CN values,
   round-trip identities, clipping and validation at the bounds, the lambda
   and ARC conversions, the weighting divergence, event separation on
   synthetic series, empty inputs and estimator comparison. No network.
2. **Offline data-fetcher contract tests.** Mock USGS NWIS and USDA Soil Data
   Access responses exercise response parsing, no-data behavior, HTTP request
   parameters and the optional-dependency error without making CI depend on a
   public service or the network.
3. **Regression tests pinned to the workshop data pack.** Every number that
   appears on a workshop slide is asserted against the data it was computed
   from, so a slide claim and the code cannot silently diverge. These read
   `tests/data`.
4. **Notebook expectation tests.** The values the Colab notebooks print for
   attendees are asserted here, so a change to the package that would alter
   what forty people see on their screens fails the build first.
5. **Data pack integrity tests.** Row counts, column names, date ranges, units
   and checksums for the 1.8 MB pack in `tests/data`. If an input file is
   regenerated or truncated, this layer says so rather than letting layer 2
   fail with a confusing numeric mismatch.

Run them:

```bash
pip install -e ".[test,data]"

pytest                              # every layer
pytest tests src/cnkit              # every layer plus the package doctests
pytest tests/test_cnkit.py tests/test_public_api_validation.py -q
pytest -m "not slow"                # skip the long fits and anything networked
pytest --cov=cnkit --cov-report=term-missing
flake8 src tests tools
```

Doctests are collected by default (`--doctest-modules`), so the quick start
above and the examples in the module docstrings are executable specifications,
not illustrations.

CI runs the whole matrix on Python 3.9 through 3.14 on Linux, plus macOS and
Windows on 3.12, and separately verifies that the single-file notebook build is
in sync:

```bash
python tools/flatten.py            # regenerate dist/cnkit_flat.py
python tools/flatten.py --check    # fails if the flat file has drifted
```

`tools/flatten.py` concatenates the five science modules into one importable
file for Colab attendees who have no pip install step. It is generated, never
edited by hand, and CI fails on drift.

## Citation

If you use `cnkit`, cite the software and the workshop it was written for.
Machine-readable metadata is in [`CITATION.cff`](CITATION.cff); GitHub renders
a formatted citation from it, and most reference managers import it directly.

Ramirez-Avila, J. J., and Kumar, S. (2026). *cnkit: curve number hydrology with
Earth Observation*, version 1.0.0. Software accompanying the workshop "Modern
Curve Number Hydrology: Fundamentals and Remote Sensing Applications", 2026
ASCE-EWRI Watershed Management Conference.

## Provenance

This is the library behind the workshop "Modern Curve Number Hydrology:
Fundamentals and Remote Sensing Applications" at the 2026 ASCE-EWRI Watershed
Management Conference, developed for the EWRI Curve Number Hydrology and Remote
Sensing Task Committees. It is released for workshop use and for anyone who
wants to reproduce the workshop's results. There is no warranty. Verify before
you design anything.

Licensed under the MIT License. See [`LICENSE`](LICENSE) and
[`CHANGELOG.md`](CHANGELOG.md).
