Skip to content

API reference

Auto-generated from the AquaScope source. Every public function, class, and method appears below, with its docstring rendered in NumPy style.

If you're looking for a guided introduction, start with Getting started or Features. This page is the exhaustive reference.


High-level API

The most common entry points live in aquascope.api:

aquascope.api

High-level convenience API for common hydrological analyses.

Provides one-liner functions wrapping AquaScope's lower-level modules. Designed for quick analyses in Jupyter notebooks and scripts.

Examples:

>>> from aquascope.api import flood_analysis, baseflow_analysis
>>> result = flood_analysis(daily_discharge, method="gev", return_periods=[10, 50, 100])
>>> bf = baseflow_analysis(daily_discharge, method="eckhardt")

flood_analysis

flood_analysis(discharge: Series, method: str = 'gev', return_periods: list[int] | None = None, ci_level: float = 0.9, regional_skew: float | None = None, **kwargs) -> FloodFreqResult

Fit a flood-frequency distribution and estimate return-period quantiles.

Parameters:

Name Type Description Default
discharge Series

Daily (or sub-daily) discharge time-series with a :class:~pandas.DatetimeIndex.

required
method str

Distribution to fit. One of "gev", "lp3", "gumbel", "gev_lmoments", or "gpd".

'gev'
return_periods list[int] | None

List of return periods (years) for which to estimate quantiles. Defaults to [2, 5, 10, 25, 50, 100].

None
ci_level float

Confidence level for bootstrap confidence intervals (GEV only).

0.9
regional_skew float | None

Optional regional skew coefficient (LP3 only).

None
**kwargs

Forwarded to the underlying fitting function.

{}

Returns:

Type Description
FloodFreqResult

Fitted distribution parameters, quantile estimates, and confidence intervals.

Raises:

Type Description
ValueError

If method is not one of the supported methods.

Source code in aquascope/api.py
def flood_analysis(
    discharge: pd.Series,
    method: str = "gev",
    return_periods: list[int] | None = None,
    ci_level: float = 0.90,
    regional_skew: float | None = None,
    **kwargs,
) -> FloodFreqResult:
    """Fit a flood-frequency distribution and estimate return-period quantiles.

    Parameters
    ----------
    discharge:
        Daily (or sub-daily) discharge time-series with a
        :class:`~pandas.DatetimeIndex`.
    method:
        Distribution to fit.  One of ``"gev"``, ``"lp3"``, ``"gumbel"``,
        ``"gev_lmoments"``, or ``"gpd"``.
    return_periods:
        List of return periods (years) for which to estimate quantiles.
        Defaults to ``[2, 5, 10, 25, 50, 100]``.
    ci_level:
        Confidence level for bootstrap confidence intervals (GEV only).
    regional_skew:
        Optional regional skew coefficient (LP3 only).
    **kwargs:
        Forwarded to the underlying fitting function.

    Returns
    -------
    FloodFreqResult
        Fitted distribution parameters, quantile estimates, and
        confidence intervals.

    Raises
    ------
    ValueError
        If *method* is not one of the supported methods.
    """
    from aquascope.hydrology.flood_frequency import (
        fit_gev,
        fit_gev_lmoments,
        fit_gpd,
        fit_gumbel,
        fit_lp3,
    )

    if method not in _FLOOD_METHODS:
        msg = f"Unknown flood-frequency method {method!r}. Choose from {sorted(_FLOOD_METHODS)}."
        raise ValueError(msg)

    if return_periods is None:
        return_periods = [2, 5, 10, 25, 50, 100]

    if method == "gev":
        return fit_gev(discharge, return_periods=return_periods, ci_level=ci_level, **kwargs)
    if method == "lp3":
        lp3_kwargs: dict = {"return_periods": return_periods, **kwargs}
        if regional_skew is not None:
            lp3_kwargs["regional_skew"] = regional_skew
        return fit_lp3(discharge, **lp3_kwargs)
    if method in ("gumbel", "gev_lmoments"):
        # `fit_gumbel` and `fit_gev_lmoments` take annual maxima -- their first
        # parameter is named `annual_maxima` -- while this function documents
        # `discharge` as a daily or sub-daily series. Passing the series
        # straight through made every daily observation an "annual" maximum, so
        # a six-year record was fitted as ~2,200 of them and the return levels
        # described the distribution of *daily* flows rather than of annual
        # floods. `fit_gev` and `fit_lp3` already resample internally; these two
        # do not, so it happens here.
        annual_maxima = _as_annual_maxima(discharge)
        if method == "gumbel":
            return fit_gumbel(annual_maxima, return_periods=return_periods, **kwargs)
        return fit_gev_lmoments(annual_maxima, return_periods=return_periods, **kwargs)
    # gpd
    return fit_gpd(discharge, return_periods=return_periods, **kwargs)

baseflow_analysis

baseflow_analysis(discharge: Series, method: str = 'lyne_hollick', **kwargs) -> BaseflowResult

Separate baseflow from quickflow using a digital filter.

Parameters:

Name Type Description Default
discharge Series

Daily discharge time-series.

required
method str

"lyne_hollick" (recursive filter, default) or "eckhardt" (two-parameter filter).

'lyne_hollick'
**kwargs

Forwarded to the filter function (e.g. alpha, n_passes).

{}

Returns:

Type Description
BaseflowResult

DataFrame of total / baseflow / quickflow plus BFI.

Raises:

Type Description
ValueError

If method is not supported.

Source code in aquascope/api.py
def baseflow_analysis(
    discharge: pd.Series,
    method: str = "lyne_hollick",
    **kwargs,
) -> BaseflowResult:
    """Separate baseflow from quickflow using a digital filter.

    Parameters
    ----------
    discharge:
        Daily discharge time-series.
    method:
        ``"lyne_hollick"`` (recursive filter, default) or ``"eckhardt"``
        (two-parameter filter).
    **kwargs:
        Forwarded to the filter function (e.g. ``alpha``, ``n_passes``).

    Returns
    -------
    BaseflowResult
        DataFrame of total / baseflow / quickflow plus BFI.

    Raises
    ------
    ValueError
        If *method* is not supported.
    """
    from aquascope.hydrology.baseflow import eckhardt, lyne_hollick

    if method not in _BASEFLOW_METHODS:
        msg = f"Unknown baseflow method {method!r}. Choose from {sorted(_BASEFLOW_METHODS)}."
        raise ValueError(msg)

    if method == "lyne_hollick":
        return lyne_hollick(discharge, **kwargs)
    return eckhardt(discharge, **kwargs)

flow_duration

flow_duration(discharge: Series, **kwargs) -> FDCResult

Compute a flow-duration curve.

Parameters:

Name Type Description Default
discharge Series

Daily discharge time-series.

required
**kwargs

Forwarded to :func:~aquascope.hydrology.flow_duration.flow_duration_curve (e.g. percentiles).

{}

Returns:

Type Description
FDCResult

Exceedance probabilities, sorted discharges, and percentile values.

Source code in aquascope/api.py
def flow_duration(discharge: pd.Series, **kwargs) -> FDCResult:
    """Compute a flow-duration curve.

    Parameters
    ----------
    discharge:
        Daily discharge time-series.
    **kwargs:
        Forwarded to :func:`~aquascope.hydrology.flow_duration.flow_duration_curve`
        (e.g. ``percentiles``).

    Returns
    -------
    FDCResult
        Exceedance probabilities, sorted discharges, and percentile values.
    """
    from aquascope.hydrology.flow_duration import flow_duration_curve

    return flow_duration_curve(discharge, **kwargs)

compute_all_signatures

compute_all_signatures(discharge: Series, **kwargs) -> SignatureReport

Compute a comprehensive set of hydrological signatures.

Parameters:

Name Type Description Default
discharge Series

Daily discharge time-series.

required
**kwargs

Forwarded to :func:~aquascope.hydrology.signatures.compute_signatures (e.g. precipitation, area_km2).

{}

Returns:

Type Description
SignatureReport

Dataclass containing ~20 hydrological signature values.

Source code in aquascope/api.py
def compute_all_signatures(discharge: pd.Series, **kwargs) -> SignatureReport:
    """Compute a comprehensive set of hydrological signatures.

    Parameters
    ----------
    discharge:
        Daily discharge time-series.
    **kwargs:
        Forwarded to :func:`~aquascope.hydrology.signatures.compute_signatures`
        (e.g. ``precipitation``, ``area_km2``).

    Returns
    -------
    SignatureReport
        Dataclass containing ~20 hydrological signature values.
    """
    from aquascope.hydrology.signatures import compute_signatures

    return compute_signatures(discharge, **kwargs)

detect_changepoints

detect_changepoints(series: ndarray | Series, method: str = 'pelt', **kwargs) -> ChangePointResult

Detect abrupt shifts in a time-series.

Parameters:

Name Type Description Default
series ndarray | Series

One-dimensional numeric data.

required
method str

Detection algorithm. One of "pelt", "cusum", "binary_segmentation", or "pettitt".

'pelt'
**kwargs

Forwarded to the detection function.

{}

Returns:

Type Description
ChangePointResult

Detected change-points, segment summaries, and test statistics.

Raises:

Type Description
ValueError

If method is not supported.

Source code in aquascope/api.py
def detect_changepoints(
    series: np.ndarray | pd.Series,
    method: str = "pelt",
    **kwargs,
) -> ChangePointResult:
    """Detect abrupt shifts in a time-series.

    Parameters
    ----------
    series:
        One-dimensional numeric data.
    method:
        Detection algorithm.  One of ``"pelt"``, ``"cusum"``,
        ``"binary_segmentation"``, or ``"pettitt"``.
    **kwargs:
        Forwarded to the detection function.

    Returns
    -------
    ChangePointResult
        Detected change-points, segment summaries, and test statistics.

    Raises
    ------
    ValueError
        If *method* is not supported.
    """
    from aquascope.analysis.changepoint import (
        ChangePointResult as CPResult,
    )
    from aquascope.analysis.changepoint import (
        binary_segmentation,
        cusum,
        pelt,
        pettitt_test,
    )

    if method not in _CHANGEPOINT_METHODS:
        msg = f"Unknown changepoint method {method!r}. Choose from {sorted(_CHANGEPOINT_METHODS)}."
        raise ValueError(msg)

    if method == "pelt":
        return pelt(series, **kwargs)
    if method == "cusum":
        return cusum(series, **kwargs)
    if method == "binary_segmentation":
        return binary_segmentation(series, **kwargs)
    # pettitt — returns ChangePoint | None; wrap into ChangePointResult
    cp = pettitt_test(series, **kwargs)
    changepoints = [cp] if cp is not None else []
    return CPResult(
        changepoints=changepoints,
        n_changepoints=len(changepoints),
        method="pettitt",
        penalty=None,
        segments=[],
    )

fit_copula

fit_copula(x: ndarray | Series, y: ndarray | Series, family: str = 'auto', **kwargs) -> CopulaResult

Fit a bivariate copula to paired observations.

Parameters:

Name Type Description Default
x ndarray | Series

Paired data arrays of equal length.

required
y ndarray | Series

Paired data arrays of equal length.

required
family str

Copula family — "auto" (best AIC), "gaussian", "clayton", "gumbel", or "frank".

'auto'
**kwargs

Forwarded to :func:~aquascope.analysis.copulas.fit_copula.

{}

Returns:

Type Description
CopulaResult

Fitted copula parameters, dependence measures, and AIC.

Raises:

Type Description
ValueError

If family is not supported.

Source code in aquascope/api.py
def fit_copula(
    x: np.ndarray | pd.Series,
    y: np.ndarray | pd.Series,
    family: str = "auto",
    **kwargs,
) -> CopulaResult:
    """Fit a bivariate copula to paired observations.

    Parameters
    ----------
    x, y:
        Paired data arrays of equal length.
    family:
        Copula family — ``"auto"`` (best AIC), ``"gaussian"``,
        ``"clayton"``, ``"gumbel"``, or ``"frank"``.
    **kwargs:
        Forwarded to :func:`~aquascope.analysis.copulas.fit_copula`.

    Returns
    -------
    CopulaResult
        Fitted copula parameters, dependence measures, and AIC.

    Raises
    ------
    ValueError
        If *family* is not supported.
    """
    from aquascope.analysis.copulas import (
        compare_copulas,
        to_pseudo_observations,
    )
    from aquascope.analysis.copulas import (
        fit_copula as _fit_copula,
    )

    if family not in _COPULA_FAMILIES:
        msg = f"Unknown copula family {family!r}. Choose from {sorted(_COPULA_FAMILIES)}."
        raise ValueError(msg)

    u, v = to_pseudo_observations(x, y)

    if family == "auto":
        results = compare_copulas(u, v)
        return results[0]  # best AIC

    return _fit_copula(u, v, family=family, **kwargs)

trend_analysis

trend_analysis(series: ndarray | Series | list[float], alpha: float = 0.05, method: str = 'original') -> MannKendallResult

Perform a Mann-Kendall trend test with Sen's slope estimation.

Parameters:

Name Type Description Default
series ndarray | Series | list[float]

Time-series data.

required
alpha float

Significance level (default: 0.05).

0.05
method str

Mann-Kendall test variant ("original", "hamed_rao", "yue_wang", "pre_whitening", "tfpw").

'original'

Returns:

Type Description
MannKendallResult

Test statistics, p-value, trend classification, and Sen's slope.

Source code in aquascope/api.py
def trend_analysis(
    series: np.ndarray | pd.Series | list[float],
    alpha: float = 0.05,
    method: str = "original",
) -> MannKendallResult:
    """Perform a Mann-Kendall trend test with Sen's slope estimation.

    Parameters
    ----------
    series:
        Time-series data.
    alpha:
        Significance level (default: ``0.05``).
    method:
        Mann-Kendall test variant (``"original"``, ``"hamed_rao"``,
        ``"yue_wang"``, ``"pre_whitening"``, ``"tfpw"``).

    Returns
    -------
    MannKendallResult
        Test statistics, p-value, trend classification, and Sen's slope.
    """
    from aquascope.analysis.trends import mann_kendall as _mann_kendall

    return _mann_kendall(series, alpha=alpha, method=method)

mann_kendall

mann_kendall(series: ndarray | Series | list[float], alpha: float = 0.05, method: str = 'original') -> MannKendallResult

Alias for :func:trend_analysis.

Source code in aquascope/api.py
def mann_kendall(
    series: np.ndarray | pd.Series | list[float],
    alpha: float = 0.05,
    method: str = "original",
) -> MannKendallResult:
    """Alias for :func:`trend_analysis`."""
    from aquascope.analysis.trends import mann_kendall as _mann_kendall

    return _mann_kendall(series, alpha=alpha, method=method)

sens_slope

sens_slope(series: ndarray | Series | list[float]) -> SensSlopeResult

Estimate Sen's slope for a time series.

Parameters:

Name Type Description Default
series ndarray | Series | list[float]

Time-series data.

required

Returns:

Type Description
SensSlopeResult

Slope, intercept, and sample counts.

Source code in aquascope/api.py
def sens_slope(
    series: np.ndarray | pd.Series | list[float],
) -> SensSlopeResult:
    """Estimate Sen's slope for a time series.

    Parameters
    ----------
    series:
        Time-series data.

    Returns
    -------
    SensSlopeResult
        Slope, intercept, and sample counts.
    """
    from aquascope.analysis.trends import sens_slope as _sens_slope

    return _sens_slope(series)

bayesian_regression

bayesian_regression(X: ndarray | DataFrame, y: ndarray | Series, degree: int = 1, **kwargs) -> PosteriorResult

Fit a Bayesian linear or polynomial regression.

Parameters:

Name Type Description Default
X ndarray | DataFrame

Feature matrix (degree=1) or 1-D predictor (degree>1).

required
y ndarray | Series

Response variable.

required
degree int

Polynomial degree. 1 uses :class:~aquascope.models.bayesian.BayesianLinearRegression; higher values use :class:~aquascope.models.bayesian.BayesianPolynomialRegression.

1
**kwargs

Forwarded to the model constructor (e.g. prior_precision).

{}

Returns:

Type Description
PosteriorResult

Posterior summaries, credible intervals, and diagnostics.

Source code in aquascope/api.py
def bayesian_regression(
    X: np.ndarray | pd.DataFrame,  # noqa: N803
    y: np.ndarray | pd.Series,
    degree: int = 1,
    **kwargs,
) -> PosteriorResult:
    """Fit a Bayesian linear or polynomial regression.

    Parameters
    ----------
    X:
        Feature matrix (degree=1) or 1-D predictor (degree>1).
    y:
        Response variable.
    degree:
        Polynomial degree.  ``1`` uses
        :class:`~aquascope.models.bayesian.BayesianLinearRegression`;
        higher values use
        :class:`~aquascope.models.bayesian.BayesianPolynomialRegression`.
    **kwargs:
        Forwarded to the model constructor (e.g. ``prior_precision``).

    Returns
    -------
    PosteriorResult
        Posterior summaries, credible intervals, and diagnostics.
    """
    from aquascope.models.bayesian import BayesianLinearRegression, BayesianPolynomialRegression

    if degree == 1:
        model = BayesianLinearRegression(**kwargs)
        return model.fit(X, y)
    model = BayesianPolynomialRegression(degree=degree, **kwargs)
    return model.fit(X, y)

ensemble_forecast

ensemble_forecast(models: list[tuple[str, object]], X_train: DataFrame, y_train: Series, X_test: DataFrame, method: str = 'stacking', **kwargs) -> np.ndarray

Train an ensemble of models and return predictions on X_test.

Parameters:

Name Type Description Default
models list[tuple[str, object]]

List of (name, estimator) tuples.

required
X_train DataFrame

Training features.

required
y_train Series

Training target.

required
X_test DataFrame

Test features.

required
method str

Ensemble strategy — "weighted", "stacking", or "adaptive".

'stacking'
**kwargs

Forwarded to the ensemble constructor.

{}

Returns:

Type Description
ndarray

Predicted values for X_test.

Raises:

Type Description
ValueError

If method is not supported.

Source code in aquascope/api.py
def ensemble_forecast(
    models: list[tuple[str, object]],
    X_train: pd.DataFrame,  # noqa: N803
    y_train: pd.Series,
    X_test: pd.DataFrame,  # noqa: N803
    method: str = "stacking",
    **kwargs,
) -> np.ndarray:
    """Train an ensemble of models and return predictions on *X_test*.

    Parameters
    ----------
    models:
        List of ``(name, estimator)`` tuples.
    X_train:
        Training features.
    y_train:
        Training target.
    X_test:
        Test features.
    method:
        Ensemble strategy — ``"weighted"``, ``"stacking"``, or
        ``"adaptive"``.
    **kwargs:
        Forwarded to the ensemble constructor.

    Returns
    -------
    numpy.ndarray
        Predicted values for *X_test*.

    Raises
    ------
    ValueError
        If *method* is not supported.
    """
    from aquascope.models.ensemble import AdaptiveEnsemble, StackingEnsemble, WeightedEnsemble

    if method not in _ENSEMBLE_METHODS:
        msg = f"Unknown ensemble method {method!r}. Choose from {sorted(_ENSEMBLE_METHODS)}."
        raise ValueError(msg)

    if method == "weighted":
        ens = WeightedEnsemble(models, **kwargs)
        ens.fit(X_train, y_train)
        return ens.predict(X_test).predictions
    if method == "stacking":
        ens = StackingEnsemble(models, **kwargs)
        ens.fit(X_train, y_train)
        return ens.predict(X_test).predictions
    # adaptive
    ens = AdaptiveEnsemble(models, **kwargs)
    ens.fit(X_train, y_train)
    return ens.update_and_predict(X_test).predictions

generate_report

generate_report(title: str, **kwargs) -> ReportBuilder

Create a pre-configured :class:~aquascope.reporting.builder.ReportBuilder.

Parameters:

Name Type Description Default
title str

Report title.

required
**kwargs

Forwarded to :class:~aquascope.reporting.builder.ReportBuilder (e.g. author, description).

{}

Returns:

Type Description
ReportBuilder

A builder instance ready for method-chaining.

Source code in aquascope/api.py
def generate_report(title: str, **kwargs) -> ReportBuilder:
    """Create a pre-configured :class:`~aquascope.reporting.builder.ReportBuilder`.

    Parameters
    ----------
    title:
        Report title.
    **kwargs:
        Forwarded to :class:`~aquascope.reporting.builder.ReportBuilder`
        (e.g. ``author``, ``description``).

    Returns
    -------
    ReportBuilder
        A builder instance ready for method-chaining.
    """
    from aquascope.reporting.builder import ReportBuilder

    return ReportBuilder(title, **kwargs)

groundwater_analysis

groundwater_analysis(levels: Series, method: str = 'trend', **kwargs) -> dict

Run a groundwater analysis on a water-level time series.

Parameters:

Name Type Description Default
levels Series

Water-level measurements with :class:~pandas.DatetimeIndex.

required
method str

Analysis type — "trend" (Mann-Kendall trend detection), "recession" (aquifer recession), "seasonal" (decomposition), or "hydrograph" (full hydrograph summary).

'trend'
**kwargs

Forwarded to the underlying function.

{}

Returns:

Type Description
dict

Result dataclass from the chosen analysis, accessed as a dict or the original dataclass depending on method.

Raises:

Type Description
ValueError

If method is not supported.

Source code in aquascope/api.py
def groundwater_analysis(
    levels: pd.Series,
    method: str = "trend",
    **kwargs,
) -> dict:
    """Run a groundwater analysis on a water-level time series.

    Parameters
    ----------
    levels:
        Water-level measurements with :class:`~pandas.DatetimeIndex`.
    method:
        Analysis type — ``"trend"`` (Mann-Kendall trend detection),
        ``"recession"`` (aquifer recession), ``"seasonal"`` (decomposition),
        or ``"hydrograph"`` (full hydrograph summary).
    **kwargs:
        Forwarded to the underlying function.

    Returns
    -------
    dict
        Result dataclass from the chosen analysis, accessed as a dict
        or the original dataclass depending on method.

    Raises
    ------
    ValueError
        If *method* is not supported.
    """
    _gw_methods = {"trend", "recession", "seasonal", "hydrograph"}
    if method not in _gw_methods:
        msg = f"Unknown groundwater method {method!r}. Choose from {sorted(_gw_methods)}."
        raise ValueError(msg)

    if method == "trend":
        from aquascope.groundwater.wells import trend_detection
        return trend_detection(levels, **kwargs)
    if method == "recession":
        from aquascope.groundwater.wells import recession_analysis
        return recession_analysis(levels, **kwargs)
    if method == "seasonal":
        from aquascope.groundwater.wells import seasonal_decomposition
        return seasonal_decomposition(levels, **kwargs)
    # hydrograph
    from aquascope.groundwater.wells import well_hydrograph
    return well_hydrograph(levels, **kwargs)

climate_downscale

climate_downscale(obs: Series, gcm_hist: Series, gcm_future: Series, method: str = 'quantile_mapping', **kwargs) -> pd.Series

Downscale a GCM projection using statistical bias correction.

Parameters:

Name Type Description Default
obs Series

Observed station data.

required
gcm_hist Series

GCM historical simulation (overlapping period with obs).

required
gcm_future Series

GCM future projection to downscale.

required
method str

Downscaling method — "delta" (additive/multiplicative), "quantile_mapping", or "qdm" (Quantile Delta Mapping).

'quantile_mapping'
**kwargs

Forwarded to the underlying function.

{}

Returns:

Type Description
Series

Bias-corrected future projection.

Raises:

Type Description
ValueError

If method is not supported.

Source code in aquascope/api.py
def climate_downscale(
    obs: pd.Series,
    gcm_hist: pd.Series,
    gcm_future: pd.Series,
    method: str = "quantile_mapping",
    **kwargs,
) -> pd.Series:
    """Downscale a GCM projection using statistical bias correction.

    Parameters
    ----------
    obs:
        Observed station data.
    gcm_hist:
        GCM historical simulation (overlapping period with *obs*).
    gcm_future:
        GCM future projection to downscale.
    method:
        Downscaling method — ``"delta"`` (additive/multiplicative),
        ``"quantile_mapping"``, or ``"qdm"`` (Quantile Delta Mapping).
    **kwargs:
        Forwarded to the underlying function.

    Returns
    -------
    pandas.Series
        Bias-corrected future projection.

    Raises
    ------
    ValueError
        If *method* is not supported.
    """
    _ds_methods = {"delta", "quantile_mapping", "qdm"}
    if method not in _ds_methods:
        msg = f"Unknown downscaling method {method!r}. Choose from {sorted(_ds_methods)}."
        raise ValueError(msg)

    if method == "delta":
        from aquascope.climate.downscaling import delta_method
        return delta_method(obs, gcm_hist, gcm_future, **kwargs)
    if method == "quantile_mapping":
        from aquascope.climate.downscaling import quantile_mapping
        return quantile_mapping(obs, gcm_hist, gcm_future, **kwargs)
    # qdm
    from aquascope.climate.downscaling import quantile_delta_mapping
    return quantile_delta_mapping(obs, gcm_hist, gcm_future, **kwargs)

climate_indices

climate_indices(precip: Series | None = None, temperature: Series | None = None, pet: Series | None = None, index: str = 'cdd', **kwargs) -> object

Compute a climate index from meteorological data.

Parameters:

Name Type Description Default
precip Series | None

Precipitation series (required for "cdd", "cwd", "pci", "drought", "pdsi").

None
temperature Series | None

Maximum temperature series (required for "heat_wave").

None
pet Series | None

Potential evapotranspiration (required for "pdsi", "aridity").

None
index str

Index to compute — "cdd" (consecutive dry days), "cwd" (consecutive wet days), "pci" (precipitation concentration), "heat_wave", "aridity", "pdsi".

'cdd'
**kwargs

Forwarded to the underlying function.

{}

Returns:

Type Description
object

Result dataclass or value from the chosen index.

Raises:

Type Description
ValueError

If index is not supported or required input is missing.

Source code in aquascope/api.py
def climate_indices(
    precip: pd.Series | None = None,
    temperature: pd.Series | None = None,
    pet: pd.Series | None = None,
    index: str = "cdd",
    **kwargs,
) -> object:
    """Compute a climate index from meteorological data.

    Parameters
    ----------
    precip:
        Precipitation series (required for ``"cdd"``, ``"cwd"``, ``"pci"``,
        ``"drought"``, ``"pdsi"``).
    temperature:
        Maximum temperature series (required for ``"heat_wave"``).
    pet:
        Potential evapotranspiration (required for ``"pdsi"``, ``"aridity"``).
    index:
        Index to compute — ``"cdd"`` (consecutive dry days),
        ``"cwd"`` (consecutive wet days), ``"pci"`` (precipitation
        concentration), ``"heat_wave"``, ``"aridity"``, ``"pdsi"``.
    **kwargs:
        Forwarded to the underlying function.

    Returns
    -------
    object
        Result dataclass or value from the chosen index.

    Raises
    ------
    ValueError
        If *index* is not supported or required input is missing.
    """
    _idx_names = {"cdd", "cwd", "pci", "heat_wave", "aridity", "pdsi"}
    if index not in _idx_names:
        msg = f"Unknown climate index {index!r}. Choose from {sorted(_idx_names)}."
        raise ValueError(msg)

    if index == "cdd":
        from aquascope.climate.indices import consecutive_dry_days
        if precip is None:
            raise ValueError("'precip' is required for CDD index.")
        return consecutive_dry_days(precip, **kwargs)
    if index == "cwd":
        from aquascope.climate.indices import consecutive_wet_days
        if precip is None:
            raise ValueError("'precip' is required for CWD index.")
        return consecutive_wet_days(precip, **kwargs)
    if index == "pci":
        from aquascope.climate.indices import precipitation_concentration_index
        if precip is None:
            raise ValueError("'precip' is required for PCI index.")
        return precipitation_concentration_index(precip, **kwargs)
    if index == "heat_wave":
        from aquascope.climate.indices import heat_wave_index
        if temperature is None:
            raise ValueError("'temperature' is required for heat_wave index.")
        return heat_wave_index(temperature, **kwargs)
    if index == "aridity":
        from aquascope.climate.indices import aridity_index
        if precip is None or pet is None:
            raise ValueError("'precip' and 'pet' are required for aridity index.")
        return aridity_index(float(precip.sum()), float(pet.sum()), **kwargs)
    # pdsi
    from aquascope.climate.indices import palmer_drought_severity_index
    if precip is None or pet is None:
        raise ValueError("'precip' and 'pet' are required for PDSI.")
    return palmer_drought_severity_index(precip, pet, **kwargs)

budyko_analysis

budyko_analysis(precipitation: float | ndarray, pet: float | ndarray, curves: list[str] | None = None, fu_omega: float = 2.0, observed_et: float | ndarray | None = None, observed_runoff: float | ndarray | None = None) -> BudykoResult

Partition long-term precipitation under the Budyko framework.

The long-term water balance P = ET + Q (storage change neglected) means a catchment is placed from either of its observed fluxes: actual evapotranspiration (observed_et) or runoff (observed_runoff, converted via ET = P - Q). Exactly one may be given.

Parameters:

Name Type Description Default
precipitation float | ndarray

Long-term mean annual precipitation (mm/yr), scalar or array.

required
pet float | ndarray

Long-term mean annual potential evapotranspiration (mm/yr).

required
curves list[str] | None

Budyko curves to evaluate. Any subset of "schreiber", "oldekop", "turc_pike", "fu_zhang". Defaults to all four.

None
fu_omega float

Fu/Zhang shape parameter omega, >= 1.

2.0
observed_et float | ndarray | None

Optional observed long-term evapotranspiration (mm/yr), requiring 0 <= observed_et <= precipitation. Mutually exclusive with observed_runoff.

None
observed_runoff float | ndarray | None

Optional observed long-term runoff (mm/yr), requiring 0 <= observed_runoff <= precipitation. Mutually exclusive with observed_et.

None

Returns:

Type Description
BudykoResult

Predicted evaporative ratios, the observed position when one observed flux is given, and the curve family over the canonical aridity grid.

Raises:

Type Description
ValueError

If curves is empty or names an unsupported curve, or both observed fluxes are given.

Source code in aquascope/api.py
def budyko_analysis(
    precipitation: float | np.ndarray,
    pet: float | np.ndarray,
    curves: list[str] | None = None,
    fu_omega: float = 2.0,
    observed_et: float | np.ndarray | None = None,
    observed_runoff: float | np.ndarray | None = None,
) -> BudykoResult:
    """Partition long-term precipitation under the Budyko framework.

    The long-term water balance ``P = ET + Q`` (storage change neglected)
    means a catchment is placed from either of its observed fluxes:
    actual evapotranspiration (*observed_et*) or runoff (*observed_runoff*,
    converted via ``ET = P - Q``).  Exactly one may be given.

    Parameters
    ----------
    precipitation:
        Long-term mean annual precipitation (mm/yr), scalar or array.
    pet:
        Long-term mean annual potential evapotranspiration (mm/yr).
    curves:
        Budyko curves to evaluate.  Any subset of ``"schreiber"``,
        ``"oldekop"``, ``"turc_pike"``, ``"fu_zhang"``.  Defaults to all four.
    fu_omega:
        Fu/Zhang shape parameter ``omega``, ``>= 1``.
    observed_et:
        Optional observed long-term evapotranspiration (mm/yr), requiring
        ``0 <= observed_et <= precipitation``.  Mutually exclusive with
        *observed_runoff*.
    observed_runoff:
        Optional observed long-term runoff (mm/yr), requiring
        ``0 <= observed_runoff <= precipitation``.  Mutually exclusive with
        *observed_et*.

    Returns
    -------
    BudykoResult
        Predicted evaporative ratios, the observed position when one observed
        flux is given, and the curve family over the canonical aridity grid.

    Raises
    ------
    ValueError
        If *curves* is empty or names an unsupported curve, or both observed
        fluxes are given.
    """
    from aquascope.hydrology.budyko import BUDYKO_CURVES, budyko

    if curves is None:
        curves = list(BUDYKO_CURVES)
    else:
        if isinstance(curves, str):
            raise ValueError("curves must be a sequence of curve names, not a single string.")
        valid = set(BUDYKO_CURVES)
        for name in curves:
            if name not in valid:
                msg = f"Unknown Budyko curve {name!r}. Choose from {BUDYKO_CURVES}."
                raise ValueError(msg)

    if observed_et is not None and observed_runoff is not None:
        raise ValueError("Provide only one of 'observed_et' and 'observed_runoff'.")

    return budyko(
        precipitation,
        pet,
        curves=curves,
        fu_omega=fu_omega,
        observed_et=observed_et,
        observed_runoff=observed_runoff,
    )

Data collectors

37 unified collectors. Every collector returns records in the same Pydantic schema.

aquascope.collectors

Data collectors for Taiwan and global water data sources.

AquastatCollector

AquastatCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect country-level water data from FAO AQUASTAT.

Parameters:

Name Type Description Default
client CachedHTTPClient | None

HTTP client instance. A default is created if None.

None
References

FAO. (2023). AQUASTAT. https://www.fao.org/aquastat/

fetch_raw

fetch_raw(country_code: str = 'all', variable_ids: list[int] | None = None, start_year: int = 2000, end_year: int = 2023, **kwargs: Any) -> list[dict]

Fetch raw AQUASTAT data from the FAOSTAT API.

Parameters:

Name Type Description Default
country_code str

ISO3 country code, or 'all' for global data.

'all'
variable_ids list[int] | None

AQUASTAT variable IDs. Defaults to all key variables.

None
start_year int

Start year (default 2000).

2000
end_year int

End year (default 2023).

2023

Returns:

Type Description
list[dict]

Raw API response records.

normalise

normalise(raw: list[dict]) -> Sequence[AquastatRecord]

Convert raw FAOSTAT response into AquastatRecord objects.

Parameters:

Name Type Description Default
raw list[dict]

Records from fetch_raw.

required

Returns:

Type Description
Sequence[AquastatRecord]

Normalised AQUASTAT records.

BaseCollector

BaseCollector(client: CachedHTTPClient | None = None)

Bases: ABC

Every collector must implement fetch_raw and normalise.

The public entry-point is collect() which chains those two steps.

fetch_raw abstractmethod

fetch_raw(**kwargs) -> Any

Fetch raw data from the upstream API.

Raises: CollectorError: When an endpoint fails hard (e.g. HTTP 4xx/5xx, transport/connection failure, invalid response body). An empty collection should only be returned when the API genuinely answered with zero results.

normalise abstractmethod

normalise(raw: Any) -> Sequence[BaseModel]

Convert raw API response into unified Pydantic records.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

Return this source's station catalog.

Sources that expose a catalog override this. bbox is (west, south, east, north) in WGS84 degrees, variable is one of :data:aquascope.schemas.station.VARIABLES, and max_items caps the result for sources whose catalog is large. The default raises so callers (and the registry drift guard) can tell "no catalog" from "empty catalog".

supports_stations classmethod

supports_stations() -> bool

True when this collector overrides :meth:stations.

collect

collect(*, as_xarray: bool = False, as_geodataframe: bool = False, **kwargs) -> Any

Fetch + normalise in one call.

By default returns the list of unified Pydantic records. Set as_xarray=True to get an xarray.Dataset (time-series) or as_geodataframe=True to get a geopandas.GeoDataFrame (point geometry) instead — both require the interop extra. The two flags are mutually exclusive.

CollectorError

CollectorError(message: str, *, source: str = '', url: str = '', status_code: int | None = None, cause: Exception | None = None)

Bases: RuntimeError

Raised when a data collector encounters a hard endpoint or network failure.

Distinguishes hard failures (HTTP 4xx/5xx, connection timeouts, malformed payloads) from genuine empty results (e.g. a valid query returning zero records). Carries the collector source name, target URL, optional HTTP status code, and the underlying cause exception.

BOMCollector

BOMCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect streamflow, water-level, and storage data from BOM Water Data Online (Australia).

Results are normalised to StreamflowReading (discharge), WaterLevelReading (gauge/storage/groundwater level), or WaterQualitySample (everything else), all with source = DataSource.BOM.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

BOM Water Data Online station catalog (KiWIS getStationList).

BOM's KiWIS instance only reliably returns parametertype_name from getStationList when the request is filtered by a single parameter type — an unfiltered request does return real coordinates for most rows (~89%), but leaves parametertype_name blank on the large majority (~76%) of them, making it useless for grouping stations by variable (confirmed live 2026-08-25: 154,895 rows, 11% with a blank latitude, 76% with a blank parametertype_name). So this issues one request per BOM parameter type (or just the ones relevant to variable, when given) and merges the results by station_no, folding each parameter type into the shared registry vocabulary via PARAMETER_VARIABLE_MAP — a station reporting discharge and water level ends up with variables=("discharge", "water_level").

bbox is sent to KiWIS as its native bbox= filter (as west,south,east,north) on every request and re-checked client-side with in_bbox in case KiWIS's box is loose. An unrecognised variable returns [] without calling the API.

Rows whose coordinates fall outside AU_SANITY_BBOX are dropped (see that constant's docstring) and counted in a single summary warning, rather than being returned as if they were real stations.

If every parameter-type request fails, raises a RuntimeError so the harvest and callers report the outage rather than recording an empty catalog. If a subset of requests fails, logs a summary warning naming the lost parameter types and returns the partial catalog.

fetch_raw

fetch_raw(station_id: str | None = None, parameter_type: str = 'Water Course Discharge', start_date: str | None = None, end_date: str | None = None, days: int | None = None, ts_name: str = DEFAULT_TS_NAME, **kwargs: Any) -> list[dict]

Fetch raw observations for a BOM station from Water Data Online.

Parameters:

Name Type Description Default
station_id str

AWRC station number (e.g. "410001" for Murrumbidgee River at Wagga Wagga).

None
parameter_type str

BOM parameter type name, e.g. "Water Course Discharge", "Water Course Level", "Storage Level".

'Water Course Discharge'
start_date str | None

Start date YYYY-MM-DD. Combined with end_date to build the from/to query window.

None
end_date str | None

End date YYYY-MM-DD. Defaults to now (UTC) when omitted.

None
days int | None

Convenience alternative to start_date — last N days from now. Ignored if start_date is supplied.

None
ts_name str

BOM time-series name. Defaults to the quality-checked merged daily mean (DMQaQc.Merged.DailyMean.24HR).

DEFAULT_TS_NAME

Returns:

Type Description
list[dict]

Raw observation rows, each merged with station/timeseries metadata. Returns an empty list if no matching time series is found or the upstream API is unreachable.

normalise

normalise(raw: list[dict]) -> list[WaterQualitySample | WaterLevelReading | StreamflowReading]

Normalise raw BOM rows into StreamflowReading (discharge), WaterLevelReading (level-type parameters), or WaterQualitySample (everything else).

BrazilANACollector

BrazilANACollector(identificador: str | None = None, senha: str | None = None, client: CachedHTTPClient | None = None, legacy_client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect telemetric streamflow, stage and rainfall data from ANA Hidroweb.

Parameters:

Name Type Description Default
identificador str | None

CPF/CNPJ registered with ANA for API access. Falls back to the ANA_HIDROWEB_IDENTIFICADOR environment variable.

None
senha str | None

Password for identificador. Falls back to ANA_HIDROWEB_SENHA.

None
client CachedHTTPClient

Injected for testing; a default client is created otherwise.

None
legacy_client CachedHTTPClient

Client for the legacy conventional-network service; a default one is created otherwise.

None
Notes

Credentials are only needed for :meth:fetch_raw's default telemetric mode. :meth:stations (public SNIRH catalog) and fetch_raw(mode="historical", ...) (legacy conventional-network series) both work without them.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None, telemetric_only: bool = True) -> list[Station]

SNIRH station catalog. telemetric_only restricts to stations with live telemetry (the ones :meth:fetch_raw can actually query); set it to False to see the full conventional network too.

fetch_raw

fetch_raw(station_ids: Sequence[str] | str | None = None, start_date: str | date | None = None, end_date: str | date | None = None, days: int = 30, mode: str = 'telemetric', variables: Sequence[str] = ('discharge',), **kwargs) -> list[dict]

Fetch readings for one or more stations.

Parameters:

Name Type Description Default
station_ids str | Sequence[str]

One or more ANA station codes (see :meth:stations). Required.

None
start_date str | date

Window bounds, YYYY-MM-DD. Defaults to the last days days when omitted (mode="telemetric" only — "historical" passes bounds straight through to ANA, and omitting them fetches each station's full period of record).

None
end_date str | date

Window bounds, YYYY-MM-DD. Defaults to the last days days when omitted (mode="telemetric" only — "historical" passes bounds straight through to ANA, and omitting them fetches each station's full period of record).

None
days int

Size of the default window when start_date is not given (mode="telemetric" only).

30
mode ('telemetric', 'historical')

"telemetric" (default) hits the credentialed HidroWebService REST API for near-real-time stage/discharge/rainfall — see module docstring. "historical" hits the legacy, no-auth HidroSerieHistorica webservice for ANA's conventional (manually read) network, which reaches decades further back but updates far less often.

"telemetric"
variables Sequence[str]

Which parameters to request in mode="historical" — any of "discharge", "water_level", "precipitation". Ignored in mode="telemetric" (which always returns all three together per ANA's response shape).

('discharge',)

CAMELSBRCollector

CAMELSBRCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect CAMELS-BR observed daily streamflow records.

fetch_raw

fetch_raw(station_ids: list[str] | None = None, start: str | None = None, end: str | None = None, **kwargs) -> list[dict]

Fetch CAMELS-BR streamflow records, optionally filtered.

Parameters:

Name Type Description Default
station_ids list[str]

Restrict to specific gauge codes (e.g. ["10500000", "11400000"]).

None
start str

ISO date strings ("YYYY-MM-DD") bounding the date range. Both are inclusive. If omitted, no date filtering is applied.

None
end str

ISO date strings ("YYYY-MM-DD") bounding the date range. Both are inclusive. If omitted, no date filtering is applied.

None

CAMELSCLCollector

CAMELSCLCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect CAMELS-CL observed daily streamflow (q_m3s_day.csv) records.

fetch_raw

fetch_raw(station_ids: list[str] | None = None, start: str | None = None, end: str | None = None, **kwargs) -> list[dict]

Fetch CAMELS-CL streamflow records, optionally filtered.

Parameters:

Name Type Description Default
station_ids list[str]

Restrict to specific gauge codes (e.g. ["1001001", "12825002"]). Filtering happens before the wide-to-long melt, so this keeps memory bounded — unlike loading all ~500 stations at once.

None
start str

ISO date strings ("YYYY-MM-DD") bounding the date range. Both are inclusive. If omitted, no date filtering is applied.

None
end str

ISO date strings ("YYYY-MM-DD") bounding the date range. Both are inclusive. If omitted, no date filtering is applied.

None

ColoradoCDSSCollector

ColoradoCDSSCollector(api_key: str | None = None, client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect telemetry streamflow data from Colorado DWR/CDSS.

fetch_raw

fetch_raw(*, abbrev: str, parameter: str = 'DISCHRG', **kwargs: Any) -> list[dict[str, Any]]

Fetch raw data from the Colorado CDSS API.

Parameters:

Name Type Description Default
abbrev str

CDSS station abbreviation. Multiple abbreviations may be supplied as a comma-separated string.

required
parameter str

CDSS telemetry parameter. DISCHRG is discharge.

'DISCHRG'
**kwargs Any

Additional CDSS query parameters, such as date filters.

{}

normalise

normalise(raw: list[dict[str, Any]]) -> Sequence[StreamflowReading]

Convert CDSS discharge readings from cfs to m³/s.

CopernicusCollector

CopernicusCollector(dataset: str | None = None, **kwargs)

Bases: BaseCollector

Fetch GloFAS river-discharge data from Copernicus CDS.

Parameters:

Name Type Description Default
dataset str

CDS dataset ID. Default is the GloFAS historical dataset.

None
Example

collector = CopernicusCollector() records = collector.collect( ... latitude=48.85, longitude=2.35, ... year="2023", month=["01", "02", "03"], ... )

fetch_raw

fetch_raw(*, latitude: float, longitude: float, year: str | list[str] = '2023', month: str | list[str] = '01', day: str | list[str] | None = None, variable: str = 'river_discharge_in_the_last_24_hours', product_type: str = 'consolidated', system_version: str = 'version_4_0') -> list[dict]

Fetch data via CDS API and return parsed records.

Parameters:

Name Type Description Default
latitude float

Site coordinates.

required
longitude float

Site coordinates.

required
year str | list[str]

Temporal selection.

'2023'
month str | list[str]

Temporal selection.

'2023'
day str | list[str]

Temporal selection.

'2023'
variable str

CDS variable name.

'river_discharge_in_the_last_24_hours'
product_type str

CDS-specific dataset options.

'consolidated'
system_version str

CDS-specific dataset options.

'consolidated'

normalise

normalise(raw: list[dict]) -> Sequence[BaseModel]

Convert parsed GRIB records into WaterQualitySample objects.

EUWFDCollector

EUWFDCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect water quality data from the EEA WISE SoE Waterbase.

Uses the DiscoData SQL API published by the European Environment Agency.

fetch_raw

fetch_raw(country: str | None = None, water_body_type: str = 'river', year: int | None = None, **kwargs) -> list[dict]

Fetch raw water quality records from the EEA DiscoData API.

Parameters: country: ISO-2 country code (e.g. "DE", "FR"). water_body_type: "river", "lake", or "groundwater". year: Calendar year to filter on.

Returns: List of raw record dicts from the API response.

normalise

normalise(raw: list[dict]) -> Sequence[WaterQualitySample]

Convert raw EEA records into unified WaterQualitySample objects.

Parameters: raw: List of dicts from fetch_raw.

Returns: Sequence of WaterQualitySample instances.

HubeauHydrometrieCollector

HubeauHydrometrieCollector(api_key: str = '', client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect real-time river level/discharge data from Hub'Eau (France).

Parameters:

Name Type Description Default
api_key str

Unused — Hub'Eau is open data with no authentication. Kept for interface parity with other collectors.

''

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

Hub'Eau referentiel/stations: every hydrometric station in service.

Hub'Eau's referentiel does not say which grandeur (H or Q) a station reports, so variables is ("water_level", "discharge") for all; the observation endpoints answer that per station. bbox goes to the API and is re-checked client side.

fetch_raw

fetch_raw(code_station: str | None = None, grandeur_hydro: str | None = None, date_debut_obs: str | None = None, date_fin_obs: str | None = None, days: int | None = None, size: int = 1000, max_items: int | None = 5000, elaborated: str | None = None, **kwargs) -> list[dict]

Fetch observations from Hub'Eau: real-time observations_tr (default) or the long elaborated series obs_elab when elaborated is set.

Parameters:

Name Type Description Default
elaborated str

An elaborated grandeur code (see ELABORATED_GRANDEURS), e.g. "QmnJ" for daily mean discharge with multi-decade history. When set, the request goes to /obs_elab; grandeur_hydro is ignored and date_debut_obs / date_fin_obs (or days) bound date_obs_elab.

None
code_station str

Hydrometric station code (e.g. "K002000101"). Sent to the API as code_entite. Without this (or a date/grandeur filter), Hub'Eau returns observations across thousands of stations nationwide.

None
grandeur_hydro str

"H" (water level) or "Q" (discharge). Omit to fetch both.

None
date_debut_obs str | None

ISO 8601 bounds, e.g. "2026-06-01T00:00:00Z". Built from days if omitted.

None
days int

Last N days from now (UTC). Ignored if date_debut_obs is given. Hub'Eau's own default lookback applies if neither is set.

None
size int

Page size (Hub'Eau's hard max is 20000).

1000
max_items int

Hard cap on total records fetched across all pages. None means no cap.

5000

GEMStatCollector

GEMStatCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect water quality data from the GEMStat Zenodo archive.

Downloads the CSV file, parses rows, and normalises into WaterQualitySample records. Supports filtering by country.

fetch_raw

fetch_raw(country: str | None = None, max_records: int = 5000, parameters: list[str] | None = None, start_date: str | None = None, end_date: str | None = None, **kwargs) -> list[dict]

Download the GEMStat Zenodo archive (once), then join station metadata with observation rows and return filtered results.

The ZIP (~200 MB) is cached to data/cache/ on first call; subsequent calls load from the local file and are fast.

Parameters:

Name Type Description Default
country str

Full or partial country name (e.g. "Germany", "Canada"). Case-insensitive substring match against the station metadata. GEMStat covers ~42 countries — Taiwan is not included.

None
max_records int

Hard cap on returned rows across all parameters (default 5 000).

5000
parameters list[str]

Parameter CSV names without .csv (e.g. ["pH", "Temperature"]). Defaults to :attr:DEFAULT_PARAMETERS.

None
start_date str

ISO date "YYYY-MM-DD" — only include rows on or after this date.

None
end_date str

ISO date "YYYY-MM-DD" — only include rows on or before this date.

None

parse_gemstat_csv staticmethod

parse_gemstat_csv(csv_content: str, max_records: int = 10000) -> list[WaterQualitySample]

Parse a GEMStat CSV string into WaterQualitySample records.

Expected columns: GEMS Station Number, Sample Date, Parameter, Analysis Result, Unit, Latitude, Longitude, Country Code, etc.

GRDCCollector

GRDCCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect GRDC in-situ (Zenodo) and RSEG satellite-extension (DaRUS) discharge records.

fetch_raw

fetch_raw(source_type: str = 'in_situ', max_items: int | None = 50000, **kwargs) -> list[dict]

Fetch raw GRDC records.

Parameters:

Name Type Description Default
source_type str

"in_situ" (default, Zenodo gauge subset) or "satellite" (RSEG, DaRUS).

'in_situ'
max_items int | None

Maximum number of records to return. None returns all records.

50000

GreeceHydroscopeCollector

GreeceHydroscopeCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect river stage, discharge and rainfall from Greece's Hydroscope network.

Parameters:

Name Type Description Default
client CachedHTTPClient

Injected for testing; a default client is created otherwise.

None

fetch_stations

fetch_stations(db: int) -> list[dict[str, Any]]

Fetch one instance's raw station list.

fetch_timeseries

fetch_timeseries(db: int) -> list[dict[str, Any]]

Fetch one instance's raw series catalog.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

Hydroscope station catalog, restricted to stations that hold data.

fetch_raw

fetch_raw(variable: str = 'discharge', station_ids: Iterable[str] | None = None, bbox: tuple[float, float, float, float] | None = None, start: str | date | None = None, end: str | date | None = None, max_stations: int | None = None, **kwargs) -> list[dict[str, Any]]

Fetch observation rows for one variable.

Parameters:

Name Type Description Default
variable str

One of "discharge", "water_level" or "precipitation".

'discharge'
station_ids iterable of str

Composite station ids (as emitted by :meth:stations). All stations holding the variable are used when omitted.

None
bbox tuple

(west, south, east, north) in WGS84 degrees.

None
start str or date

Inclusive window, pushed to the server as a download path suffix. Both must be given for the server-side filter to apply; a single bound is applied client-side instead.

None
end str or date

Inclusive window, pushed to the server as a download path suffix. Both must be given for the server-side filter to apply; a single bound is applied client-side instead.

None
max_stations int

Cap on the number of stations fetched. Left uncapped by default, but worth setting: the full daily stage archive is ~50 MB.

None

normalise

normalise(raw: list[dict[str, Any]]) -> Sequence[BaseModel]

Convert rows into the model matching each row's variable.

Discharge becomes :class:StreamflowReading in m3/s, water level becomes :class:WaterLevelReading in m, and rainfall becomes :class:ClimateReading. Rows whose declared unit is not convertible are dropped rather than assumed.

GreeceOpenhiCollector

GreeceOpenhiCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect live discharge, stage, rainfall, climate and water quality from Greece's OpenHi.net network.

Parameters:

Name Type Description Default
client CachedHTTPClient

Injected for testing; a default client is created otherwise.

None

fetch_stations

fetch_stations() -> list[dict[str, Any]]

Fetch every station record.

fetch_groups

fetch_groups(station_id: int | str) -> list[dict[str, Any]]

Fetch one station's time-series groups (each carries a variable).

fetch_series

fetch_series(station_id: int | str, group_id: int | str) -> list[dict[str, Any]]

Fetch the series inside one group.

unit_symbol

unit_symbol(unit_id: int | None) -> str

Resolve a unit id to its symbol, with the lookup table cached.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

OpenHi station catalog, with each station's variables resolved.

Resolving variables costs one request per station, since what a station measures is only visible in its groups. The client's cache absorbs that on repeat calls.

fetch_raw

fetch_raw(variable: str = 'discharge', station_ids: Iterable[str | int] | None = None, bbox: tuple[float, float, float, float] | None = None, start: str | date | None = None, end: str | date | None = None, max_stations: int | None = None, latest_only: bool = False, **kwargs) -> list[dict[str, Any]]

Fetch observation rows for one variable.

Parameters:

Name Type Description Default
variable str

An AquaScope variable name present in :data:VARIABLE_MAP, e.g. "discharge", "water_level", "precipitation", "climate", "water_quality".

'discharge'
station_ids iterable

OpenHi station ids. All stations carrying the variable otherwise.

None
bbox tuple

(west, south, east, north) in WGS84 degrees.

None
start str or date

Pushed to the API as start_date / end_date. Strongly recommended: the largest series here is 12.9 MB unwindowed.

None
end str or date

Pushed to the API as start_date / end_date. Strongly recommended: the largest series here is 12.9 MB unwindowed.

None
max_stations int

Cap on stations fetched.

None
latest_only bool

Use /bottom/ to take just each series' most recent row. Cheap enough to sweep the whole network for a liveness check.

False

normalise

normalise(raw: list[dict[str, Any]]) -> Sequence[BaseModel]

Convert rows into the model matching each row's variable.

Discharge becomes :class:StreamflowReading in m3/s and stage :class:WaterLevelReading in metres, both converted from whatever unit the API declares; a unit that is not convertible drops the row rather than being assumed. Rainfall, evaporation and the atmospheric and soil variables become :class:ClimateReading, and the in-water chemistry becomes :class:WaterQualitySample.

IndiaWRISCollector

IndiaWRISCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collector for India WRIS river water level data.

IrelandOPWCollector

IrelandOPWCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect real-time river/lake water level from Ireland's OPW (waterlevel.ie).

Parameters:

Name Type Description Default
client CachedHTTPClient

Injected for testing; a default client is created otherwise.

None

fetch_stations

fetch_stations() -> list[dict[str, Any]]

Fetch the GeoJSON list of active OPW stations.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

OPW station catalog from the waterlevel.ie GeoJSON feed.

fetch_raw

fetch_raw(stations: list[dict[str, Any]] | None = None, max_stations: int | None = None, **kwargs) -> list[dict[str, Any]]

Fetch raw water-level rows for every valid OPW station.

Parameters:

Name Type Description Default
stations list of dict

Pre-fetched GeoJSON features, to avoid a second station-list call (mainly for tests). Fetched automatically if omitted.

None
max_stations int

Cap on the number of stations to fetch series for. None means all valid stations — useful to limit during testing or exploratory use, since there are hundreds of stations.

None

JapanMLITCollector

JapanMLITCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect water data from Japan MLIT Water Information System.

Supports water-level, discharge, water-quality, and rainfall observations. Results are normalised to WaterQualitySample records with source = DataSource.JAPAN_MLIT.

fetch_raw

fetch_raw(station_id: str | None = None, prefecture: str | None = None, parameter: str = 'water_level', start_date: str | None = None, end_date: str | None = None, **kwargs: Any) -> list[dict]

Fetch raw observation data from the MLIT Water Information System.

Parameters:

Name Type Description Default
station_id str | None

MLIT station code (e.g. "305041281005030").

None
prefecture str | None

Prefecture name in English (e.g. "Tokyo"). Mapped to a numeric code via PREFECTURE_CODES.

None
parameter str

One of water_level, discharge, water_quality, rainfall.

'water_level'
start_date str | None

Start date in ISO format (YYYY-MM-DD).

None
end_date str | None

End date in ISO format (YYYY-MM-DD).

None
**kwargs Any

Additional keyword arguments forwarded to the HTTP request.

{}

Returns:

Type Description
list[dict]

Raw observation records. Returns an empty list when the upstream API is unreachable or no data matches.

Raises:

Type Description
ValueError

If parameter is not one of the supported types.

normalise

normalise(raw: list[dict]) -> list[WaterQualitySample]

Normalise raw MLIT data into WaterQualitySample records.

Parameters:

Name Type Description Default
raw list[dict]

Raw records as returned by fetch_raw.

required

Returns:

Type Description
list[WaterQualitySample]

Unified water-quality sample records.

KoreaWAMISCollector

KoreaWAMISCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect water data from Korea WAMIS Open API.

Supports water-level, discharge, water-quality, and dam-storage observations. Results are normalised to WaterQualitySample records with source = DataSource.KOREA_WAMIS.

fetch_raw

fetch_raw(station_id: str | None = None, basin: str | None = None, parameter: str = 'water_level', start_date: str | None = None, end_date: str | None = None, **kwargs: Any) -> list[dict]

Fetch raw observation data from the WAMIS Open API.

Parameters:

Name Type Description Default
station_id str | None

WAMIS station code.

None
basin str | None

Basin name in English (e.g. "Han"). Mapped to the Korean name via KOREA_MAJOR_BASINS.

None
parameter str

One of water_level, discharge, water_quality, dam_storage.

'water_level'
start_date str | None

Start date in ISO format (YYYY-MM-DD).

None
end_date str | None

End date in ISO format (YYYY-MM-DD).

None
**kwargs Any

Additional keyword arguments forwarded to the HTTP request.

{}

Returns:

Type Description
list[dict]

Raw observation records. Returns an empty list when the upstream API is unreachable or no data matches.

Raises:

Type Description
ValueError

If parameter is not one of the supported types.

normalise

normalise(raw: list[dict]) -> list[WaterQualitySample]

Normalise raw WAMIS data into WaterQualitySample records.

Parameters:

Name Type Description Default
raw list[dict]

Raw records as returned by fetch_raw.

required

Returns:

Type Description
list[WaterQualitySample]

Unified water-quality sample records.

NOAANWPSCollector

NOAANWPSCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

fetch_raw

fetch_raw(*, lid: str | None = None, bbox: tuple[float, float, float, float] | None = None, srid: str = 'EPSG_4326', catfim: bool = False, **_: Any) -> Any

Fetch NWPS payload for one lid or enumerate a list of lids discovered by bbox. The first 5 LIDs will be iterated through _build_combined_data_dictionary

normalise

normalise(data_dictionary: dict[str, Any]) -> Sequence[StreamflowReading]

Convert NWPS observed entries into StreamflowReading records.

OpenMeteoCollector

OpenMeteoCollector(mode: str = 'weather', **kwargs)

Bases: BaseCollector

Fetch weather, climate reanalysis, and river-discharge data from Open-Meteo.

Parameters:

Name Type Description Default
mode str

"weather" (default), "forecast", "flood" (GloFAS discharge) or "climate" (CMIP6 HighResMIP daily projections, 1950-2050; models is a comma-separated list).

'weather'
Example

collector = OpenMeteoCollector(mode="weather") records = collector.collect( ... latitude=25.03, longitude=121.57, ... start_date="2023-01-01", end_date="2023-12-31", ... daily=["temperature_2m_mean", "precipitation_sum"], ... )

fetch_raw

fetch_raw(*, latitude: float, longitude: float, start_date: str | None = None, end_date: str | None = None, daily: list[str] | None = None, hourly: list[str] | None = None, forecast_days: int = 7, models: str | None = None) -> dict

Call the Open-Meteo API and return the raw JSON response.

Parameters:

Name Type Description Default
latitude float

Site coordinates.

required
longitude float

Site coordinates.

required
start_date str | None

ISO-8601 date strings (required for archive mode).

None
end_date str | None

ISO-8601 date strings (required for archive mode).

None
daily list[str] | None

Daily variables to request (e.g. ["precipitation_sum"]).

None
hourly list[str] | None

Hourly variables (e.g. ["river_discharge"]).

None
forecast_days int

Number of forecast days (only for mode="forecast").

7
models str | None

Weather-mode reanalysis to read through the /archive endpoint: "best_match" (Open-Meteo's blend: ERA5-Land where available, ERA5 otherwise; the only choice that serves every daily variable), "era5_land", "era5", ... Default (None) is ERA5 via /era5.

None

normalise

normalise(raw: dict) -> Sequence[BaseModel]

Convert Open-Meteo JSON into unified WaterQualitySample records.

Weather / climate variables are stored as WaterQualitySample with parameter set to the variable name (e.g. precipitation_sum).

PegelonlineCollector

PegelonlineCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect recent water-level and discharge readings from PEGELONLINE.

PEGELONLINE exposes raw measurements for only the most recent 31 days. Stations are addressed by UUID because station names and numbers are not guaranteed to be unique or stable.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

All PEGELONLINE gauges (stations.json?includeTimeseries=true).

The API has no spatial filter, so bbox is applied client side. variables comes from the advertised timeseries codes (W water level, Q discharge).

fetch_raw

fetch_raw(station_id: str | None = None, timeseries: str | Sequence[str] | None = None, days: int = 30, start: str | None = None, end: str | None = None, **kwargs: Any) -> dict[str, Any]

Fetch station metadata and recent W/Q measurements.

Args: station_id: PEGELONLINE station UUID. timeseries: W (water level), Q (discharge), or both. days: Relative history window when start is omitted (1-31). start: Optional API timestamp or ISO-8601 duration. end: Optional API timestamp.

normalise

normalise(raw: dict[str, Any]) -> Sequence[WaterLevelReading | StreamflowReading]

Convert PEGELONLINE W/Q measurements to AquaScope records.

PolandIMGWCollector

PolandIMGWCollector(client: CachedHTTPClient | None = None, cache_dir: Path | None = None, timeout: float = 120.0)

Bases: BaseCollector

IMGW-PIB river gauges: live network state and the daily archive from 1951.

Parameters:

Name Type Description Default
client CachedHTTPClient

Used for the live API. Injected for testing.

None
cache_dir Path

Where archive zips are kept between runs (default data/cache/poland_imgw).

None
timeout float

Per-download timeout for archive zips, seconds.

120.0

fetch_live

fetch_live() -> list[dict[str, Any]]

The current state of every station, one request, cached for the run.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

The network from the live API: every station carries coordinates.

variables reflects what the station is reporting now; the archive may hold more (a gauge whose discharge rating lapsed still has decades of discharge behind it).

fetch_raw

fetch_raw(variable: str = 'discharge', station_ids: Iterable[str | int] | None = None, bbox: tuple[float, float, float, float] | None = None, start: str | date | None = None, end: str | date | None = None, max_stations: int | None = None, latest_only: bool = False, **kwargs: Any) -> list[dict[str, Any]]

Daily rows for one variable from the archive, or the live reading with latest_only.

start/end select archive files by hydrological year and month, then rows by calendar date; the default window is the last 365 days. Files for years not yet published are skipped, so a window reaching into the current hydrological year simply stops at the last October.

normalise

normalise(raw: list[dict[str, Any]]) -> Sequence[BaseModel]

Discharge to :class:StreamflowReading (m3/s), stage to :class:WaterLevelReading (metres above the gauge datum), water temperature to :class:WaterQualitySample (°C).

SDG6Collector

SDG6Collector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect SDG 6 indicator data per country/year from the UN-Stats API.

fetch_raw

fetch_raw(indicator_codes: list[str] | None = None, country_codes: str | None = None, page_size: int = 200, **kwargs) -> list[dict]

Fetch SDG 6 indicator records.

Parameters:

Name Type Description Default
indicator_codes list[str] | None

e.g. ["6.4.2", "6.4.1"]. Defaults to all 9 SDG 6 indicators.

None
country_codes str | None

Comma-separated ISO3 or M49 numeric codes, e.g. "USA,DEU,IND". Omit to return data for all countries.

None
page_size int

Records per API page (max 5000 per UN docs).

200

SouthAfricaDWSCollector

SouthAfricaDWSCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect verified river discharge and water level from South Africa DWS.

discharge requests the DWS daily mean series (D_AVG_FR). water_level requests point observations (COR_LEVEL).

fetch_raw

fetch_raw(station_id: str, variable: str = 'discharge', start_date: str | date | datetime | None = None, end_date: str | date | datetime | None = None, days: int | None = None, use_cache: bool = False, **kwargs: Any) -> list[dict[str, Any]]

Fetch and parse one DWS river series.

Parameters use the agency station code without the DWS 100.00 series suffix, for example C1H001. A supplied suffix is accepted and normalised so it is never duplicated.

Caching is disabled by default because DWS can return an application error document with HTTP 200; callers may opt in once the upstream service is known to be healthy.

TaiwanCivilIoTCollector

TaiwanCivilIoTCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect real-time water resource data from Taiwan's Civil IoT SensorThings API.

The entity parameter is consumed by :meth:fetch_raw, not by __init__; see that method's docstring for valid values.

fetch_raw

fetch_raw(entity: str = 'Datastreams', top: int = 100, expand: str | None = None, start_date: str | None = None, end_date: str | None = None, **kwargs) -> list[dict]

Fetch SensorThings entities.

Parameters:

Name Type Description Default
entity str

"Things" | "Datastreams" | "Observations"

'Datastreams'
top int

Max items per page.

100
expand str

OData $expand clause. If not given, one is built from start_date / end_date.

None
start_date str

ISO date string "YYYY-MM-DD" — filters phenomenonTime ge.

None
end_date str

ISO date string "YYYY-MM-DD" — filters phenomenonTime le.

None

normalise

normalise(raw: list[dict]) -> Sequence[WaterQualitySample]

Normalise SensorThings Datastreams with their latest Observation.

TaiwanCWACollector

TaiwanCWACollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect daily climate observations from Taiwan CWA stations via CODIS.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

CWA station catalog from CODIS station_list (manned, agricultural, automatic).

Every station reports precipitation; the manned (cwb) network also carries the full climate set. extra["attribute"] keeps the CODIS group (cwb / agr / auto) so callers can pick long-archive stations.

fetch_raw

fetch_raw(station_ids: list[str] | str | None = None, start: str | None = None, end: str | None = None, stn_type: str | None = None, **kwargs) -> list[dict]

Fetch daily climate records for one or more stations.

Parameters:

Name Type Description Default
station_ids list[str] | str

CWA station identifiers (e.g. ["466920"] for Taipei). Defaults to Taipei. A single string is accepted.

None
start str

Inclusive ISO dates ("YYYY-MM-DD"). Defaults to the last 30 days. Requests are paged in calendar-year windows to stay polite with the unauthenticated API.

None
end str

Inclusive ISO dates ("YYYY-MM-DD"). Defaults to the last 30 days. Requests are paged in calendar-year windows to stay polite with the unauthenticated API.

None
stn_type str

CODIS station attribute. By default it follows from the station id (:func:codis_station_type): "cwb" for the staffed synoptic network (numeric ids, long archives), "auto_C0" / "auto_C1" for automatic stations, "agr" for the agricultural network. Asked with the wrong one, CODIS returns an empty answer, not an error.

None

TaiwanDataGovCollector

TaiwanDataGovCollector(dataset_id: str = DATASET_WATER_LEVEL, client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect real-time water level data from Taiwan's open government data platform (data.gov.tw).

Parameters:

Name Type Description Default
dataset_id str

WRA Open Data UUID. Use :data:DATASET_WATER_LEVEL (default) for real-time river water level or :data:DATASET_GROUNDWATER for real-time groundwater level.

DATASET_WATER_LEVEL

fetch_raw

fetch_raw(limit: int | None = None, offset: int = 0, **kwargs) -> list[dict]

Fetch records from the WRA Open Data API for the configured dataset.

The API returns the full dataset in one response; offset and an optional limit are applied client-side. By default all records are returned.

TaiwanMOENVCollector

TaiwanMOENVCollector(api_key: str = '', dataset_id: str = RIVER_WQ_DATASET, client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect river water-quality monitoring data from Taiwan MOENV.

Parameters:

Name Type Description Default
api_key str

Free key obtained at https://data.moenv.gov.tw/en/apikey

''
dataset_id str

Dataset identifier (default: river water quality AQX_P_07).

RIVER_WQ_DATASET

fetch_raw

fetch_raw(limit: int = 1000, offset: int = 0, **kwargs) -> list[dict]

Page through MOENV open-data endpoint and return raw records.

TaiwanWRAGroundwaterCollector

TaiwanWRAGroundwaterCollector(statistic: str = 'average', na_value: float | None = None, with_metadata: bool = True, client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect annual groundwater-level statistics from WRA.

Source: WRA annual-groundwater dataset (GROUNDWATER_ANNUAL_DATASET), ~992 wells nationwide, one record per well per year (1992-present), with annual average / maximum-daily / minimum-daily water levels.

Parameters:

Name Type Description Default
statistic str

Which annual statistic to emit as water_level_m: "average" (default), "maximum", or "minimum". Use "average" for trend analysis and "minimum" for drought-severity work.

'average'
na_value float | None

How to represent missing values (WRA's -999998 sentinels and empty fields). None (default) drops the record entirely, which is the safe choice for trend/drawdown analysis. Pass float("nan") to keep a placeholder year, or a number (e.g. 0.0) to substitute a constant — but note that substituting 0 injects a real-looking zero-elevation reading that will distort trend and drawdown results.

None
with_metadata bool

When True (default), also fetch the WRA well-status (井況) dataset and join it on the well identifier, populating each reading's aquifer_name (groundwater zone), well_depth_m, station_name, and location (TWD97 coordinates converted to WGS84 via pyproj; left None if pyproj is not installed). Set False to skip the extra request when only the level series is needed.

True

TaiwanWRAGroundwaterDailyCollector

TaiwanWRAGroundwaterDailyCollector(zones: Sequence[str] | None = None, stations: Sequence[str] | None = None, start: str | date | None = None, end: str | date | None = None, aggregate: str = 'monthly', window_years: int = 5, with_metadata: bool = True, client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect DAILY groundwater-level series from the WRA gweb HydroInfo portal.

Unlike :class:TaiwanWRAGroundwaterCollector (annual statistics from the open-data API), this collector reaches the sub-annual series the open-data API does not expose, which is what drought-propagation methods (monthly SGI, SPI/SPEI lag analysis) require. Per-well daily records span roughly 2005-2025, with the Zhuoshui (Choushui) fan back to 1997.

The portal throttles request bursts and resets connections, so this collector rate-limits and caches every POST. A full national pull is thousands of requests; scope with zones or stations for a focused study. Re-runs are served from cache.

Parameters:

Name Type Description Default
zones list[str] | None

Aquifer zones to include, by English alias (e.g. "zhuoshui fan"), Chinese name, or numeric code ("050"). None (default) pulls all 11 zones (heavy; expect thousands of requests).

None
stations list[str] | None

Explicit station identifiers (e.g. "07010211"). When given, zone discovery is skipped and only these wells are fetched.

None
start str | date | None

Clip the date range ("YYYY-MM-DD"). None uses each well's full available span (read from the portal's history summary).

None
end str | date | None

Clip the date range ("YYYY-MM-DD"). None uses each well's full available span (read from the portal's history summary).

None
aggregate str

"monthly" (default) emits one reading per well per month (the mean of that month's daily values: the input to SGI). "daily" emits one reading per well per day (far larger).

'monthly'
window_years int

Chunk size (years) for the windowed chart pulls. Default 5.

5
with_metadata bool

When True (default), join the open-data 井況 well-status dataset to populate each reading's location (TWD97 coordinates converted to WGS84) and well_depth_m. The gweb station id is matched to the suffix of the open-data wellidentifier after GW (falling back to the well name). Set False to skip the extra open-data request.

True
Notes

water_level_m carries the WRA-reported groundwater level relative to its datum (Taiwan Vertical Datum), NOT depth below ground surface; values are signed. Verify the datum per aquifer before interpreting drawdown.

TaiwanWRAReservoirCollector

TaiwanWRAReservoirCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect daily reservoir operation data from WRA.

TaiwanWRAWaterLevelCollector

TaiwanWRAWaterLevelCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect real-time water-level readings from WRA river stations. Updated every 10 minutes at source.

TaiwanWRAFhyCollector

TaiwanWRAFhyCollector(data_type: str = 'water', client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect real-time hydrological data from the WRA 防災資訊網 (Fhy) API.

Parameters:

Name Type Description Default
data_type str

One of "water" (water level), "rainfall", or "flow" (river discharge). Defaults to "water".

'water'

fetch_raw

fetch_raw(**kwargs) -> list[dict]

Fetch real-time data for the configured data type.

TaiwanWRAIoTCollector

TaiwanWRAIoTCollector(data_type: str = 'groundwater', client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect real-time hydrological data from the WRA IoT open-data API.

Parameters:

Name Type Description Default
data_type str

Currently only "groundwater" (地下水位) is supported without an API key. Defaults to "groundwater".

'groundwater'

fetch_raw

fetch_raw(**kwargs) -> list[dict]

Fetch the station list (each with a nested Measurements array).

CachedHTTPClient.get_json strips any BOM / leading whitespace and checks Content-Type before parsing, so a non-JSON body (e.g. an HTML error page from a stale path) surfaces as ValueError with a preview of the response rather than an opaque JSONDecodeError.

normalise

normalise(raw: list[dict]) -> Sequence[WaterQualitySample]

Flatten each station's nested Measurements into samples.

One WaterQualitySample is emitted per (station, measurement) pair — today that's one reading per station, but the API models it as a list so a station could in principle report more than one physical quantity in the future.

UKEACollector

UKEACollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect readings from the UK Environment Agency hydrology API.

Parameters:

Name Type Description Default
client CachedHTTPClient | None

Optional preconfigured HTTP client used for API requests. If not passed, a default client is used.

None

Initialise the collector.

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = None) -> list[Station]

EA Hydrology API station catalog (id/stations.json).

The API filters by observedProperty and by lat/long bounds; each item lists every observed property at the site, which becomes variables. dateOpened / dateClosed become the period.

fetch_raw

fetch_raw(collection: str | None = '15min', observed_property: str | None = None, measure: str | None = None, station: str | None = None, station_wiski_id: str | None = None, bbox: str | None = None, min_date: str | None = None, max_date: str | None = None, days: int | None = None, limit: int = 10000, max_items: int | None = 2000, **kwargs) -> list[dict]

Fetch readings from the UK Environment Agency Hydrology API.

Parameters:

Name Type Description Default
collection str | None

Frequency of data collection. Supported values are "15min" and "daily". Note that the available collection periods may vary by station and observed property.

'15min'
observed_property str | None

Normalised observed property name. Supported values: "waterFlow", "waterLevel", "rainfall", "groundwaterLevel".

None
measure str | None

Exact measure identifier. When supplied, the collector uses the measure directly and ignores station and collection filters.

None
station str | None

Station SUID, used to collect data from a specific station. A 36-character GUID-style identifier.

None
station_wiski_id str | None

WISKI station identifier, primarily used to differentiate between co-located stations that share the same station SUID.

None
bbox str | None

Bounding box for location-based queries. Expressed as "min-long,min-lat,max-long,max-lat".

None
min_date str | None

Inclusive lower bound for the requested date range. If not accompanied by a max_date, data from the next days days are collected (until today).

None
max_date str | None

Inclusive upper bound for the requested date range. If the max_date is in the future, the value defaults to today's date. If not accompanied by a min_date, data from the previous days days are collected.

None
days int | None

Relative lookback window in days used when explicit date range is not used absent. If there is no date range and no days value passed, data from the last 30 days are passed.

None
limit int

Number of records requested per page from the API.

10000
max_items int | None

Maximum number of records to return after the metadata row is prepended. None means no hard cap.

2000

Returns:

Type Description
list[dict]

Raw API items with a prepended metadata dictionary used by :meth:normalise.

normalise

normalise(raw: list[dict]) -> Sequence[WaterQualitySample] | Sequence[WaterLevelReading] | Sequence[StreamflowReading]

Convert raw UK EA readings into AquaScope schema objects.

Parameters:

Name Type Description Default
raw list[dict]

Raw payload returned by :meth:fetch_raw, including a prepended request-metadata dictionary.

required

Returns:

Type Description
Sequence[WaterQualitySample] | Sequence[WaterLevelReading] | Sequence[StreamflowReading]

Normalised water-quality, water-level, or streamflow records. If the observed property requested does not map to a schema object, or there is no raw data available, an empty list is returned.

USGSCollector

USGSCollector(api_key: str | None = None, client: CachedHTTPClient | None = None, *, lookup_catchment_area: bool = True)

Bases: BaseCollector

Collect daily-value water data from USGS via the Water Data OGC API (v1).

Parameters:

Name Type Description Default
api_key str | None

USGS API key for higher rate limits (get one at https://api.waterdata.usgs.gov/signup/). If omitted, the collector reads the USGS_API_KEY environment variable. With neither, the attribute is "DEMO_KEY" and requests go out with no key at all (the API answers keyless calls at a lower rate limit).

None

keyed property

keyed: bool

True when a real USGS API key is set (not the keyless DEMO_KEY placeholder).

fetch_raw

fetch_raw(collection: str = 'daily', datetime_range: str | None = None, days: int | None = None, limit: int = 10000, bbox: str | None = None, max_items: int | None = 2000, **kwargs) -> list[dict]

Fetch features from a USGS Water Data OGC collection (v1).

Parameters:

Name Type Description Default
collection str

"daily" | "continuous" | "field-measurements" (or any other v1 collection). The older names "sta", "iv", "dv" and "discrete" are mapped onto their v1 collection.

'daily'
datetime_range str

Explicit ISO 8601 interval "<start>/<end>" (USGS does NOT accept ISO durations like P7D). If omitted, an interval is built from days.

None
days int

Last N days from now (UTC). Defaults to 30 when datetime_range is not supplied.

None
limit int

Max features per page (the API allows up to 50,000). USGS advises one page per query: every further page re-runs the query.

10000
bbox str

Bounding box filter "minLon,minLat,maxLon,maxLat" (WGS84). Without this the API returns data for every US monitoring location, which can require hundreds of paginated requests.

None
max_items int

Hard cap on total records fetched (across all pages). Keeps response times predictable. None means no cap.

2000
**kwargs

Filters: station_id (or sites / monitoring_location_id), parameter, statCd (the statistic, for example "00003" for the daily mean), stateCd, countyCd, huc. skip_geometry=True drops the point geometry from every feature and properties=[...] narrows the fields returned; both make a long record a smaller download.

{}

stations

stations(*, bbox: tuple[float, float, float, float] | None = None, variable: str | None = None, max_items: int | None = 20000) -> list[Station]

USGS monitoring locations with daily-value time series.

Built from the keyless OGC time-series-metadata collection (one feature per daily series, with geometry, parameter code and period of record), joined to monitoring-locations for names. Without a bbox this walks the whole national network; keep max_items unless you mean it.

WaPORCollector

WaPORCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect satellite-based ET data from FAO WaPOR v3.

Parameters:

Name Type Description Default
client CachedHTTPClient | None

HTTP client instance. A default is created if None.

None
References

FAO. (2024). WaPOR v3. https://www.fao.org/in-action/remote-sensing-for-water-productivity/

fetch_raw

fetch_raw(bbox: tuple[float, float, float, float] | None = None, start_date: str = '2020-01-01', end_date: str = '2020-12-31', variable: str = 'RET', **kwargs: Any) -> list[dict]

Fetch raw WaPOR raster catalogue/summary data.

Parameters:

Name Type Description Default
bbox tuple[float, float, float, float] | None

Bounding box as (west, south, east, north) in decimal degrees.

None
start_date str

ISO date string for start of period.

'2020-01-01'
end_date str

ISO date string for end of period.

'2020-12-31'
variable str

WaPOR variable code ('AETI', 'NPP', 'RET').

'RET'

Returns:

Type Description
list[dict]

Raw API response records.

normalise

normalise(raw: list[dict]) -> Sequence[WaPORObservation]

Convert raw WaPOR response into WaPORObservation records.

Parameters:

Name Type Description Default
raw list[dict]

Records from fetch_raw.

required

Returns:

Type Description
Sequence[WaPORObservation]

Normalised WaPOR observations.

WQPCollector

WQPCollector(client: CachedHTTPClient | None = None)

Bases: BaseCollector

Collect discrete water quality data from the US Water Quality Portal.

Supports filtering by state, county, characteristic (parameter), date range, and bounding box.

fetch_raw

fetch_raw(state_code: str | None = None, characteristic_name: str | Sequence[str] | None = None, start_date: str | None = None, end_date: str | None = None, bbox: str | None = None, max_results: int = 1000, site_id: str | Sequence[str] | None = None, **kwargs) -> list[dict]

Fetch water quality results from WQP.

Parameters:

Name Type Description Default
state_code str | None

e.g. "US:06" for California.

None
characteristic_name str | Sequence[str] | None

e.g. "Dissolved oxygen (DO)", "pH"; a list asks for several at once.

None
site_id str | Sequence[str] | None

WQP monitoring location id(s), e.g. "USGS-01646500"; a list asks for several.

None
start_date str | None

"MM-DD-YYYY" format.

None
end_date str | None

"MM-DD-YYYY" format.

None
bbox str | None

Bounding box: "west,south,east,north" in decimal degrees.

None
max_results int

Limit number of results (WQP default returns CSV).

1000

AI engine: methodology recommender

aquascope.ai_engine

AI-powered research methodology recommendation engine.

AgentResult dataclass

AgentResult(challenge_spec: ChallengeSpec, model_used: str = '', forecast: DataFrame | None = None, risk_assessment: dict | None = None, status: dict | None = None, anomalies: DataFrame | None = None, explanation: str = '', steps: list[str] = list())

Result of an end-to-end agent run.

HydroAgent

HydroAgent(default_model: str | None = None)

Orchestrator that parses a natural-language query and executes the corresponding challenge.

Parameters:

Name Type Description Default
default_model str | None

Override the auto-recommended model. If None, the ModelRecommender picks the top model for the challenge type.

None
Example

agent = HydroAgent() result = agent.solve("Drought monitoring near Sahel at lat 15, lon 0") print(result.explanation)

solve

solve(query: str, data: DataFrame | None = None, extra_data: dict[str, DataFrame] | None = None) -> AgentResult

Parse query, load data, pick model, and run the challenge.

Parameters:

Name Type Description Default
query str

Natural-language challenge description.

required
data DataFrame | None

Primary data (discharge / precipitation). If None and coordinates are detected, the agent tries to fetch data from Open-Meteo.

None
extra_data dict[str, DataFrame] | None

Additional named DataFrames (e.g. {"et": et_df}).

None

Returns:

Type Description
AgentResult

explain

explain(result: AgentResult) -> str

Produce a human-readable summary of an AgentResult.

ResearchMethodology dataclass

ResearchMethodology(id: str, name: str, category: str, description: str, applicable_parameters: list[str] = list(), data_requirements: list[str] = list(), typical_scale: str = '', complexity: str = '', references: list[str] = list(), tags: list[str] = list())

Describes a single research methodology applicable to water studies.

ModelRecommendation dataclass

ModelRecommendation(model_id: str, rank: int, challenge_type: str, task_type: str, rationale: str)

A single model recommendation.

ModelRecommender

Recommend predictive models for a given challenge type and task.

Example

rec = ModelRecommender() picks = rec.recommend("flood", "forecast") picks[0].model_id 'prophet'

recommend

recommend(challenge_type: str, task_type: str = 'forecast', top_k: int = 3) -> list[ModelRecommendation]

Return ranked model recommendations.

Parameters:

Name Type Description Default
challenge_type str

One of flood, drought, water_quality.

required
task_type str

Task within the challenge: forecast, anomaly, analysis, index.

'forecast'
top_k int

Maximum number of recommendations to return.

3

Returns:

Type Description
list[ModelRecommendation]

ChallengePlanner

Parse natural-language descriptions into structured challenge specs.

Example

planner = ChallengePlanner() spec = planner.parse("Forecast flooding on the Niger River at lat 13.5, lon 2.1") spec.challenge_type 'flood'

parse

parse(query: str) -> ChallengeSpec

Parse a natural-language query into a ChallengeSpec.

Parameters:

Name Type Description Default
query str

Free-text description of the challenge.

required

Returns:

Type Description
ChallengeSpec

Structured challenge with type, variables, and location.

ChallengeSpec dataclass

ChallengeSpec(challenge_type: str = 'unknown', variables: list[str] = list(), latitude: float | None = None, longitude: float | None = None, location_name: str | None = None, forecast_days: int = 7, raw_query: str = '', confidence: float = 0.0)

Structured representation of a parsed challenge request.

DatasetProfile dataclass

DatasetProfile(parameters: list[str] = list(), n_records: int = 0, n_stations: int = 0, time_span_years: float = 0.0, geographic_scope: str = '', data_sources: list[str] = list(), research_goal: str = '', keywords: list[str] = list())

Summary of a collected dataset, used as input to the recommender.

Recommendation dataclass

Recommendation(methodology: ResearchMethodology, score: float, rationale: str = '')

A single methodology recommendation with a relevance score.

RecommendationResult dataclass

RecommendationResult(recommendations: list[Recommendation], mode: str = 'rule_based', provider: str = '', model: str = '', error: str = '')

Recommendations plus how they were produced.

mode is "llm" when a language model actually answered, and "rule_based" when the heuristic scorer produced the list. When the LLM was requested but could not be used, error holds a human-readable reason so callers can show it instead of silently degrading.

get_methodology

get_methodology(method_id: str) -> ResearchMethodology | None

Look up a methodology by ID.

search_methodologies

search_methodologies(parameters: list[str] | None = None, category: str | None = None, tags: list[str] | None = None, scale: str | None = None) -> list[ResearchMethodology]

Filter the catalogue by parameters, category, tags, or scale.

recommend

recommend(profile: DatasetProfile, top_k: int = 5, min_score: float = 20.0) -> list[Recommendation]

Return the top-k methodology recommendations for the given dataset profile.

Parameters:

Name Type Description Default
profile DatasetProfile
required
top_k int
5
min_score float

Minimum relevance score to include.

20.0

Returns:

Type Description
list[Recommendation] sorted by descending score.

recommend_with_llm

recommend_with_llm(profile: DatasetProfile, top_k: int = 5, model: str = 'gpt-4o-mini', api_key: str | None = None, base_url: str | None = None, timeout: float = 120.0) -> list[Recommendation]

Use an LLM to provide more nuanced methodology recommendations.

Falls back to rule-based if the LLM call fails. Use :func:recommend_with_llm_detailed when you need to know whether the LLM actually answered, and why it did not.

recommend_with_llm_detailed

recommend_with_llm_detailed(profile: DatasetProfile, top_k: int = 5, model: str = 'gpt-4o-mini', api_key: str | None = None, base_url: str | None = None, timeout: float = 120.0) -> RecommendationResult

Use an LLM for nuanced methodology recommendations, reporting what happened.

Unlike :func:recommend_with_llm, this never degrades silently: on any failure it still returns rule-based recommendations, but mode is "rule_based" and error explains why the LLM was not used, so a UI or CLI can show the reason to the user.

Supported providers (detected from base_url): - OpenAI (base_url=None or api.openai.com) - HuggingFace serverless inference (router.huggingface.co) — free tier - Groq (api.groq.com) — free tier - Ollama local (localhost) — uses native /api/chat to avoid openai-client quirks - Any other OpenAI-compatible endpoint


Hydrological analysis

Flood-frequency analysis, baseflow separation, rating curves, signatures.

aquascope.hydrology

AquaScope hydrology module.

Standard hydrological analysis tools:

  • Flow duration curves and low-flow statistics (Q95, 7Q10, 30Q5)
  • Baseflow separation (Lyne–Hollick, Eckhardt digital filters)
  • Recession analysis (segment identification + MRC fitting)
  • Flood frequency analysis (GEV, Log-Pearson Type III)
  • Stage-discharge rating curves (power-law fit, segmented curves, shift detection)

Quick start::

from aquascope.hydrology import flow_duration_curve, lyne_hollick, recession_analysis, fit_gev

fdc = flow_duration_curve(discharge_series)
print(f"Q95 = {fdc.percentiles[95]:.2f} m³/s")

bf = lyne_hollick(discharge_series)
print(f"BFI = {bf.bfi:.2f}")

rec = recession_analysis(discharge_series)
print(f"Recession constant = {rec.recession_constant:.1f} days")

ffa = fit_gev(discharge_series)
print(f"100-year flood = {ffa.return_periods[100]:.1f} m³/s")

BaseflowResult dataclass

BaseflowResult(df: DataFrame, bfi: float, method: str)

Result of baseflow separation.

Attributes:

Name Type Description
df DataFrame

DataFrame with total, baseflow, quickflow columns and the original DatetimeIndex.

bfi float

Baseflow Index — ratio of total baseflow to total discharge.

method str

Name of the filter used.

BudykoResult dataclass

BudykoResult(aridity_index: float | ndarray, predicted: dict[str, float | ndarray], observed_evaporative_ratio: float | ndarray | None = None, observed_deviation: dict[str, float | ndarray] | None = None, aridity_grid: ndarray = (lambda: _DEFAULT_ARIDITY_GRID.copy())(), curves: dict[str, ndarray] = dict())

Predicted evaporative ratios under the Budyko framework.

Attributes:

Name Type Description
aridity_index float | ndarray

Aridity index (PET / P) at the query point(s): a float for scalar inputs, an array of the input shape otherwise.

predicted dict[str, float | ndarray]

Predicted evaporative ratio (ET / P) per requested curve, evaluated at aridity_index.

observed_evaporative_ratio (float | ndarray, optional)

Observed evaporative ratio (ET / P) when observed_et or observed_runoff was given.

observed_deviation (dict[str, float | ndarray], optional)

Observed ratio minus predicted ratio per curve; positive means the catchment sits above the curve (more evaporative than predicted).

aridity_grid ndarray

The aridity domain the curves were evaluated over: the canonical [0.02, 4.0] domain, extended when an observed aridity index lies outside it so the family spans the plotted points.

curves dict[str, ndarray]

Predicted evaporative ratio per requested curve over aridity_grid, ready for plotting.

EMAResult dataclass

EMAResult(return_periods: dict[int, float] = dict(), distribution: str = '', params: tuple = (), annual_max: Series | None = None, confidence_intervals: dict[int, tuple[float, float]] = dict(), n_bootstrap: int | None = None, n_bootstrap_discarded: int | None = None, n_censored: int = 0, n_observed: int = 0, weighted_skew: float | None = None, low_outlier_threshold: float | None = None)

Bases: FloodFreqResult

Result of Expected Moments Algorithm flood frequency analysis.

Extends :class:FloodFreqResult with censoring and EMA-specific fields.

Attributes: n_censored: Number of censored (zero-flow / low-outlier) observations. n_observed: Number of non-censored observations. weighted_skew: Weighted skew coefficient (station + regional). None when no regional skew was supplied. low_outlier_threshold: MGB low-outlier threshold (real-space). None when no outliers were detected.

FloodFreqResult dataclass

FloodFreqResult(return_periods: dict[int, float] = dict(), distribution: str = '', params: tuple = (), annual_max: Series | None = None, confidence_intervals: dict[int, tuple[float, float]] = dict(), n_bootstrap: int | None = None, n_bootstrap_discarded: int | None = None)

Result of flood frequency analysis.

Attributes:

Name Type Description
return_periods dict[int, float]

Mapping of return period (years) → estimated discharge.

distribution str

Name of the fitted distribution.

params tuple

Distribution parameters (shape, loc, scale).

annual_max Series | None

The annual maximum series used for fitting.

confidence_intervals dict[int, tuple[float, float]]

Optional mapping of return period → (lower, upper) 90 % CI.

n_bootstrap int | None

Number of bootstrap resamples drawn (when CI estimation is enabled).

n_bootstrap_discarded int | None

Number of bootstrap resample fits discarded due to parameter bounds.

GoodnessOfFitResult dataclass

GoodnessOfFitResult(statistic: float = 0.0, p_value: float = 1.0, test_name: str = '', distribution: str = '', reject_h0: bool = False)

Result of a goodness-of-fit test.

Attributes: statistic: Test statistic value. p_value: Associated p-value. test_name: Name of the test. distribution: Distribution that was tested. reject_h0: True if H₀ (data follows distribution) is rejected at α = 0.05.

NonStationaryGEVResult dataclass

NonStationaryGEVResult(loc_intercept: float = 0.0, loc_trend: float = 0.0, scale: float = 1.0, shape: float = 0.0, return_levels: dict[float, ndarray] = dict(), years: ndarray = (lambda: np.array([]))(), aic: float = 0.0, bic: float = 0.0, trend_significant: bool = False, lr_statistic: float = 0.0, p_value: float = 1.0, aic_stationary: float = 0.0, stationary_params: tuple[float, float, float] = (0.0, 0.0, 1.0))

Result of non-stationary GEV fit.

Attributes: loc_intercept: Intercept of the linear location model. loc_trend: Trend in the location parameter (per year). scale: Scale parameter. shape: Shape parameter (scipy sign convention). return_levels: Mapping of return period → array of return levels over time. years: The year values used for fitting. aic: Akaike information criterion. bic: Bayesian information criterion. trend_significant: True if the trend p-value < 0.05 (likelihood-ratio test). lr_statistic, p_value: The likelihood-ratio statistic and its chi-squared p-value. aic_stationary: AIC of the stationary GEV fitted by the same likelihood. stationary_params: (shape, loc, scale) of that stationary GEV.

RegionalResult dataclass

RegionalResult(growth_curve: dict[float, float] = dict(), index_flood: dict[str, float] = dict(), regional_return_levels: dict[str, dict[float, float]] = dict(), discordancy: dict[str, float] = dict(), heterogeneity: float = 0.0)

Result of regional frequency analysis.

Attributes: growth_curve: Mapping of return period → growth factor. index_flood: Mapping of site id → index flood (mean annual max). regional_return_levels: Mapping of site id → {return period → level}. discordancy: Mapping of site id → discordancy statistic Dᵢ. heterogeneity: Heterogeneity H statistic.

FDCResult dataclass

FDCResult(exceedance: ndarray, discharge: ndarray, percentiles: dict[float, float] = dict())

Result of a flow duration curve analysis.

Attributes:

Name Type Description
exceedance ndarray

Exceedance probability array (0–100 %).

discharge ndarray

Sorted discharge values (descending).

percentiles dict[float, float]

Mapping of exceedance % → discharge value (e.g. {95: 1.23}).

RatingCurveResult dataclass

RatingCurveResult(a: float, b: float, h0: float, r_squared: float, rmse: float, n_points: int, residuals: ndarray, stage_range: tuple[float, float], segments: list[RatingSegment] | None = None)

Result of rating curve fitting.

Attributes: a: Power-law coefficient. b: Power-law exponent. h0: Stage offset (datum correction). r_squared: Coefficient of determination. rmse: Root mean squared error. n_points: Number of stage-discharge pairs used. residuals: Array of residuals (observed - predicted). stage_range: Tuple of (min_stage, max_stage). segments: Segment parameters for segmented curves, or None.

RatingSegment dataclass

RatingSegment(stage_min: float, stage_max: float, a: float, b: float, h0: float, r_squared: float)

A segment of a segmented rating curve.

Attributes: stage_min: Lower bound of the segment stage range. stage_max: Upper bound of the segment stage range. a: Power-law coefficient. b: Power-law exponent. h0: Stage offset (datum correction). r_squared: Coefficient of determination for the segment.

RecessionResult dataclass

RecessionResult(segments: list[RecessionSegment] = list(), recession_constant: float = 0.0, r_squared: float = 0.0, half_life_days: float = 0.0)

Result of recession analysis.

Attributes:

Name Type Description
segments list[RecessionSegment]

List of identified recession segments.

recession_constant float

Fitted exponential decay constant k in Q(t) = Q₀·e^(−t/k).

r_squared float

R² of the master recession curve fit.

half_life_days float

Time in days for discharge to halve (k·ln(2)).

RecessionSegment dataclass

RecessionSegment(start: Timestamp, end: Timestamp, discharge: ndarray)

A single recession segment.

Attributes:

Name Type Description
start Timestamp

Start timestamp.

end Timestamp

End timestamp.

discharge ndarray

Discharge values during the recession.

SignatureReport dataclass

SignatureReport(mean_flow: float, median_flow: float, q5: float, q95: float, q5_q95_ratio: float, cv: float, iqr: float, high_flow_frequency: float, high_flow_duration: float, q_peak_mean: float, low_flow_frequency: float, low_flow_duration: float, baseflow_index: float, zero_flow_fraction: float, peak_month: int, seasonality_index: float, rising_limb_density: float, flashiness_index: float, mean_recession_constant: float, fdc_slope: float = 0.0, runoff_ratio: float | None = None, elasticity: float | None = None)

Complete set of hydrological signatures for a streamflow record.

Attributes are grouped by the aspect of the flow regime they describe. Fields set to None require optional inputs (e.g. precipitation).

eckhardt

eckhardt(discharge: Series, *, alpha: float = 0.98, bfi_max: float = 0.8) -> BaseflowResult

Separate baseflow using the Eckhardt two-parameter digital filter.

Parameters:

Name Type Description Default
discharge Series

Daily discharge series with a DatetimeIndex.

required
alpha float

Recession constant (typically 0.95–0.99).

0.98
bfi_max float

Maximum baseflow index — depends on aquifer type: - 0.80 for perennial streams with porous aquifers - 0.50 for ephemeral streams with porous aquifers - 0.25 for perennial streams with hard-rock aquifers

0.8

Returns:

Name Type Description
A class:`BaseflowResult` with separated components and BFI.

lyne_hollick

lyne_hollick(discharge: Series, *, alpha: float = 0.925, n_passes: int = 3) -> BaseflowResult

Separate baseflow using the Lyne–Hollick recursive digital filter.

Parameters:

Name Type Description Default
discharge Series

Daily discharge series with a DatetimeIndex.

required
alpha float

Filter parameter (0 < α < 1). Higher values yield less baseflow. Default 0.925 is the value recommended by Nathan & McMahon (1990).

0.925
n_passes int

Number of forward/backward filter passes (typically 3).

3

Returns:

Name Type Description
A class:`BaseflowResult` with separated components and BFI.

ukih

ukih(discharge: Series, *, block_size: int = 5) -> BaseflowResult

Separate baseflow using the UKIH smoothed-minima (sliding-interval) method.

The record is divided into non-overlapping blocks of block_size days. The minimum discharge in each block is a candidate turning point if 0.9 times its value is less than both neighbouring block minima. Baseflow is then linearly interpolated between turning points and capped at the observed discharge.

Parameters:

Name Type Description Default
discharge Series

Daily discharge series with a DatetimeIndex.

required
block_size int

Number of days per non-overlapping block (N). Default 5, as recommended by the Institute of Hydrology (1980).

5

Returns:

Name Type Description
A class:`BaseflowResult` with separated components and BFI.
References

Institute of Hydrology (1980). Low Flow Studies Report No. 1. Wallingford, UK.

anderson_darling_test

anderson_darling_test(data: ndarray, distribution: str, params: tuple) -> GoodnessOfFitResult

Anderson-Darling goodness-of-fit test for a fitted distribution.

Parameters: data: Observed sample. distribution: Distribution name (e.g. "gev", "gumbel"). params: Distribution parameters as accepted by the scipy distribution.

Returns: A :class:GoodnessOfFitResult.

coverage_probability

coverage_probability(discharge: Series, distribution: str = 'gev', ci_level: float = 0.9, n_splits: int = 10, n_boot: int = 200) -> float

Estimate the coverage probability of confidence intervals.

Split data into n_splits folds, compute bootstrap CIs on each training set, and check what fraction of test observations fall within those CIs.

Parameters:

Name Type Description Default
discharge Series

Annual maximum discharge series.

required
distribution str

Distribution key.

'gev'
ci_level float

Nominal confidence level (default 0.90).

0.9
n_splits int

Number of cross-validation folds (default 10).

10
n_boot int

Number of bootstrap samples per fold (default 200).

200

Returns:

Type Description
float

Observed coverage probability (0–1).

cramer_von_mises_test

cramer_von_mises_test(data: ndarray, distribution: str, params: tuple) -> GoodnessOfFitResult

Cramér–von Mises goodness-of-fit test.

Parameters: data: Observed sample. distribution: Distribution name. params: Distribution parameters.

Returns: A :class:GoodnessOfFitResult.

expected_moments_algorithm

expected_moments_algorithm(annual_max: Series | ndarray, *, perception_thresholds: list[tuple[float, float]] | None = None, zero_threshold: float = 0.0, regional_skew: float | None = None, regional_skew_mse: float = 0.302, return_periods: list[int] | None = None) -> EMAResult

Expected Moments Algorithm (EMA) for LP3 flood frequency analysis.

Implements the EMA procedure of Cohn et al. (1997) for incorporating censored observations (zero-flow years, low outliers, historical floods) into the LP3 parameter estimation. This is the preferred method in USGS Bulletin 17C (England et al., 2018).

Parameters: annual_max: Annual maximum series. May contain zero / negative values which will be treated as censored. perception_thresholds: Optional list of (lower, upper) pairs defining the perception interval for each observation. When None the algorithm automatically treats observations ≤ zero_threshold as left-censored and the remainder as exactly observed. zero_threshold: Values ≤ this are treated as censored (default 0.0). regional_skew: Regional / generalised skew for weighted skew. regional_skew_mse: MSE of the regional skew estimate. return_periods: Return periods to estimate. Defaults to standard set.

Returns: An :class:EMAResult with LP3 quantile estimates, confidence intervals, and censoring metadata.

Raises: ValueError: If fewer than 5 total observations.

References: England, J. F. Jr., Cohn, T. A., Faber, B. A., Stedinger, J. R., Thomas, W. O. Jr., Veilleux, A. G., Kiang, J. E., & Mason, R. R. Jr. (2018). Guidelines for determining flood flow frequency — Bulletin 17C. USGS TM 4-B5. https://doi.org/10.3133/tm4B5

Cohn, T. A., Lane, W. L., & Baier, W. G. (1997). An algorithm for
computing moments-based flood quantile estimates when historical
flood information is available. Water Resources Research, 33(9),
2089-2096. https://doi.org/10.1029/96WR03706

fit_gev

fit_gev(discharge: Series, *, return_periods: list[int] | None = None, ci_level: float | None = 0.9, max_abs_shape: float = 0.5) -> FloodFreqResult

Fit a Generalized Extreme Value (GEV) distribution via MLE with L-moments seeding.

Uses L-moments parameter estimates to seed maximum likelihood estimation and constrains the shape parameter to max_abs_shape (default 0.5) to prevent unstable tail estimates on short or moderate records (n < 50). If the MLE fit produces degenerate or unplausible shape parameters, it falls back to the robust L-moments fit.

Parameters:

Name Type Description Default
discharge Series

Daily discharge time series or annual maxima series.

required
return_periods list[int] | None

Return periods in years (default: [2, 5, 10, 25, 50, 100, 200, 500]).

None
ci_level float | None

Confidence level for bootstrap CIs (default 0.90).

0.9
max_abs_shape float

Maximum allowed absolute value for GEV shape parameter |c| (default 0.5).

0.5

Returns:

Name Type Description
A class:`FloodFreqResult` with quantile estimates.

Raises:

Type Description
ValueError

If fewer than 5 annual maxima are available.

fit_gev_lmoments

fit_gev_lmoments(annual_maxima: ndarray | Series, return_periods: list[float] | None = None) -> FloodFreqResult

Fit GEV distribution using L-moments method.

More robust than MLE for small samples (n < 50). The shape parameter k is estimated from L-skewness using the Hosking (1997) approximation:

c = 2 / (3 + t3) − ln2 / ln3
k ≈ 7.8590 c + 2.9554 c²

Parameters: annual_maxima: Array of annual maximum values. return_periods: Return periods in years. Defaults to standard set.

Returns: A :class:FloodFreqResult with fitted parameters and return levels.

Raises: ValueError: If fewer than 5 values are provided.

fit_gpd

fit_gpd(exceedances: ndarray | Series, threshold: float, return_periods: list[float] | None = None, total_observations: int | None = None) -> FloodFreqResult

Fit Generalised Pareto Distribution using Peaks-Over-Threshold method.

Parameters: exceedances: Values above the threshold (already filtered). threshold: The threshold used for POT selection. return_periods: Return periods in years. total_observations: Total number of observations used to compute exceedance rate. If None, assumed equal to len(exceedances).

Returns: A :class:FloodFreqResult with fitted parameters and return levels.

Raises: ValueError: If fewer than 10 exceedances are provided.

fit_gumbel

fit_gumbel(annual_maxima: ndarray | Series, return_periods: list[float] | None = None) -> FloodFreqResult

Fit Gumbel (Type I) extreme value distribution.

Special case of GEV with shape=0. Uses scipy.stats.gumbel_r (MLE).

Gumbel CDF: F(x) = exp(-exp(-(x - loc) / scale))

Parameters: annual_maxima: Array of annual maximum values. return_periods: Return periods in years. Defaults to standard set.

Returns: A :class:FloodFreqResult with fitted parameters and return levels.

Raises: ValueError: If fewer than 5 values are provided.

fit_lp3

fit_lp3(discharge: Series, *, return_periods: list[int] | None = None, regional_skew: float | None = None, regional_skew_mse: float = 0.302, ci_level: float = 0.9, zero_threshold: float = 0.0) -> FloodFreqResult

Fit a Log-Pearson Type III distribution (Bulletin 17C approach).

When regional_skew is provided the station skew is adjusted using the inverse-variance weighted average described in Bulletin 17C §5.2.4 (England et al., 2018). Confidence intervals are computed via the variance-of-estimate approach (Bulletin 17C §6).

Parameters: discharge: Daily discharge series with a DatetimeIndex. return_periods: Return periods to estimate. Defaults to standard set. regional_skew: Generalised / regional skew coefficient. When None (default) the station skew is used unmodified for backward compatibility. regional_skew_mse: Mean-square error of the regional skew estimate. Default 0.302 is the USGS nationwide value. ci_level: Confidence level for return-period intervals (0 < ci < 1). zero_threshold: Values ≤ this are excluded before fitting.

Returns: A :class:FloodFreqResult with quantile estimates and optional CIs.

Raises: ValueError: If fewer than 5 annual maxima or all values ≤ zero_threshold.

References: England, J. F. Jr., Cohn, T. A., Faber, B. A., Stedinger, J. R., Thomas, W. O. Jr., Veilleux, A. G., Kiang, J. E., & Mason, R. R. Jr. (2018). Guidelines for determining flood flow frequency — Bulletin 17C. U.S. Geological Survey Techniques and Methods 4-B5. https://doi.org/10.3133/tm4B5

fit_nonstationary_gev

fit_nonstationary_gev(annual_maxima: ndarray | Series, years: ndarray | Series, return_periods: list[float] | None = None, max_abs_shape: float | None = 0.5) -> NonStationaryGEVResult

Fit GEV with time-varying location: loc(t) = mu0 + mu1 * (t − t̄).

The trend significance is assessed via a likelihood-ratio test comparing the non-stationary model to a stationary GEV fitted by the same likelihood. The likelihood is written in the Coles convention (shape xi, heavy tail for xi > 0); the result reports the shape in the scipy convention (c = -xi) and the return levels use it as such.

Both fits start from scipy's stationary maximum-likelihood estimate (the non-stationary one with no trend), so the non-stationary likelihood is never worse than the stationary one and the test statistic is not negative. max_abs_shape bounds |xi| (0.5 by default, the plausible range for floods, Martins and Stedinger 2000), which keeps short or bootstrap samples from running to an absurd tail; None leaves it free.

Parameters: annual_maxima: Array of annual maximum values. years: Corresponding year values (same length as annual_maxima). return_periods: Return periods in years. Defaults to standard set. max_abs_shape: Bound on the absolute Coles shape, or None.

Returns: A :class:NonStationaryGEVResult.

Raises: ValueError: If input arrays differ in length or have fewer than 10 values.

fit_weibull_min

fit_weibull_min(annual_minima: ndarray | Series, return_periods: list[float] | None = None) -> FloodFreqResult

Fit Weibull distribution to annual minima for low-flow frequency analysis.

Uses scipy.stats.weibull_min (MLE). Return periods relate to the probability of flows below a given level.

Parameters: annual_minima: Array of annual minimum values. return_periods: Return periods in years. Defaults to standard set.

Returns: A :class:FloodFreqResult with fitted parameters and return levels.

Raises: ValueError: If fewer than 5 values are provided.

grubbs_beck_test

grubbs_beck_test(annual_max: ndarray, *, alpha: float = 0.1) -> tuple[float, np.ndarray]

Multiple Grubbs-Beck (MGB) test for low-outlier detection.

Implements the iterative procedure described in Bulletin 17C Appendix 6 (Cohn et al., 2013). The test repeatedly identifies the smallest observation that is significantly low relative to the remaining sample.

Parameters: annual_max: 1-D array of annual maximum values (all positive). alpha: Significance level for the test.

Returns: A 2-tuple (threshold, mask) where threshold is the low-outlier cutoff (log10 scale back-transformed) and mask is a boolean array with True for observations identified as low outliers.

References: Cohn, T. A., England, J. F. Jr., Berenbrock, C. E., Mason, R. R., Stedinger, J. R., & Lamontagne, J. R. (2013). A generalized Grubbs-Beck test statistic for detecting multiple potentially influential low outliers in flood series. Water Resources Research, 49(8), 5047-5058.

Grubbs, F. E. & Beck, G. (1972). Extension of sample sizes and
percentage points for significance tests of outlying observations.
Technometrics, 14(4), 847-854.

leave_one_out_cv

leave_one_out_cv(discharge: Series, distribution: str = 'gev', return_periods: list[int] | None = None) -> dict

Leave-one-out cross-validation for flood frequency fits.

For each held-out year, the distribution is fitted on the remaining years and the held-out observation is compared against the fitted median (T = 2-year return level).

Parameters:

Name Type Description Default
discharge Series

Annual maximum discharge series (with a DatetimeIndex).

required
distribution str

Distribution key: "gev", "lp3", "gumbel", "gpd", "weibull".

'gev'
return_periods list[int]

Return periods used internally. Defaults to [2] (median).

None

Returns:

Type Description
dict

Keys: 'rmse', 'bias', 'mae', 'predictions', 'observations'.

lmoments_from_sample

lmoments_from_sample(data: ndarray) -> dict[str, float]

Compute L-moments (L1–L4) and L-moment ratios (t3, t4) from a sample.

L-moments are linear combinations of probability weighted moments (PWMs).

Parameters: data: 1-D array of observations.

Returns: Dictionary with keys L1, L2, L3, L4, t3, t4.

Raises: ValueError: If fewer than 4 observations are provided.

probability_plot_correlation

probability_plot_correlation(data: ndarray, distribution: str, params: tuple) -> float

Probability Plot Correlation Coefficient (PPCC).

Computes the Pearson correlation between the sorted observations and the corresponding theoretical quantiles. Values close to 1 indicate a good fit.

Parameters: data: Observed sample. distribution: Distribution name. params: Distribution parameters.

Returns: PPCC value (between 0 and 1 for reasonable fits).

regional_frequency_analysis

regional_frequency_analysis(sites: dict[str, ndarray], return_periods: list[float] | None = None) -> RegionalResult

L-moment based regional frequency analysis (Hosking & Wallis method).

Steps: 1. Compute L-moments for each site. 2. Discordancy test (flag sites with unusual L-moments). 3. Heterogeneity measure (H < 1 → acceptably homogeneous). 4. Fit regional growth curve using weighted regional L-moments. 5. Combine with site-specific index flood for return levels.

Parameters: sites: Mapping of site identifier → annual maxima array. return_periods: Return periods in years. Defaults to standard set.

Returns: A :class:RegionalResult.

Raises: ValueError: If fewer than 2 sites are provided.

select_pot_threshold

select_pot_threshold(data: ndarray | Series, method: str = 'mean_residual') -> float

Select optimal threshold for Peaks-Over-Threshold analysis.

Parameters: data: Array of observations. method: Selection method — "mean_residual", "percentile" (95th percentile), or "sqrt_rule" (mean + 1.5 × std).

Returns: Optimal threshold value.

Raises: ValueError: If method is not recognised.

weighted_skew

weighted_skew(station_skew: float, regional_skew: float, n: int, regional_mse: float = 0.302) -> float

Compute weighted skew per Bulletin 17C §5.2.4.

Combines the station skew Gs with a generalised / regional skew Gr using inverse-variance weights:

.. math::

G_w = w_1 G_s + w_2 G_r

where w_1 = MSE_r / (MSE_s + MSE_r) and w_2 = 1 − w_1.

Parameters: station_skew: Station skew coefficient Gs. regional_skew: Regional / generalised skew Gr. n: Number of annual maximum observations. regional_mse: Mean-square error of the regional skew estimate. Default 0.302 is the USGS nationwide value from Bulletin 17C.

Returns: Weighted skew coefficient Gw.

References: England, J. F. Jr. et al. (2018). Guidelines for determining flood flow frequency — Bulletin 17C. USGS TM 4-B5. https://doi.org/10.3133/tm4B5

flow_duration_curve

flow_duration_curve(discharge: Series, *, percentiles: list[float] | None = None) -> FDCResult

Compute a flow duration curve.

Parameters:

Name Type Description Default
discharge Series

Time-series of discharge values (any DatetimeIndex).

required
percentiles list[float] | None

Exceedance percentiles to extract. Defaults to [5, 10, 25, 50, 75, 90, 95, 99].

None

Returns:

Name Type Description
A class:`FDCResult` containing sorted discharges and extracted
percentile values.

low_flow_stat

low_flow_stat(discharge: Series, *, n_day: int = 7, return_period: int = 10) -> float

Compute nQm low-flow statistic (e.g. 7Q10).

The nQm is the minimum n-day rolling average that occurs with a return period of m years, estimated using the Weibull plotting position.

Parameters:

Name Type Description Default
discharge Series

Daily discharge series with a DatetimeIndex.

required
n_day int

Rolling window size in days.

7
return_period int

Return period in years.

10

Returns:

Type Description
The estimated nQm value in the same units as the input discharge.

Raises:

Type Description
ValueError

If there are fewer than 3 complete water years.

cross_validate_rating

cross_validate_rating(stage: ndarray, discharge: ndarray, k_folds: int = 5) -> dict

K-fold cross-validation of a rating curve fit.

Parameters: stage: Stage measurements. discharge: Discharge measurements. k_folds: Number of folds (default 5).

Returns: Dict with 'mean_rmse', 'std_rmse', 'mean_r2', and 'fold_results' (list of per-fold dicts).

Raises: ValueError: If inputs are invalid.

detect_rating_shift

detect_rating_shift(stage: ndarray, discharge: ndarray, timestamps: ndarray | DatetimeIndex, window_size: int = 20) -> list[dict]

Detect temporal shifts in the stage-discharge relationship.

Fits rolling-window rating curves and compares the residual variance of successive windows using a chi-squared test. Significant changes are flagged as rating shifts.

Parameters: stage: Stage measurements. discharge: Corresponding discharge measurements. timestamps: Observation timestamps. window_size: Number of observations per rolling window.

Returns: List of dicts, each containing 'timestamp', 'shift_magnitude', and 'p_value'.

Raises: ValueError: If inputs are invalid.

export_hec_ras

export_hec_ras(result: RatingCurveResult, filepath: str | Path) -> None

Export a rating curve to HEC-RAS compatible format.

Writes a simple table of stage-discharge pairs at regular intervals spanning the fitted stage range.

Parameters: result: Fitted :class:RatingCurveResult. filepath: Output file path.

fit_rating_curve

fit_rating_curve(stage: ndarray, discharge: ndarray, h0: float | None = None) -> RatingCurveResult

Fit a power-law rating curve Q = a * (H - H₀)^b.

If h0 is None, it is optimised together with a and b using :func:scipy.optimize.curve_fit.

Parameters: stage: Water level (stage) measurements. discharge: Corresponding discharge measurements. h0: Optional fixed stage offset. If None, estimated from data.

Returns: :class:RatingCurveResult with fitted parameters and diagnostics.

Raises: ValueError: If fewer than 5 stage-discharge pairs are provided, discharge contains negative values, or arrays contain NaN.

fit_segmented_rating_curve

fit_segmented_rating_curve(stage: ndarray, discharge: ndarray, n_segments: int = 2, breakpoints: list[float] | None = None) -> RatingCurveResult

Fit a multi-segment rating curve with breakpoints.

Each segment is fitted independently as a power-law. If breakpoints are not provided, optimal breakpoints are found by minimising total RMSE over a grid of candidate values.

Parameters: stage: Water level (stage) measurements. discharge: Corresponding discharge measurements. n_segments: Number of segments (default 2). breakpoints: Explicit breakpoint stage values. Length must equal n_segments - 1. If None, breakpoints are optimised.

Returns: :class:RatingCurveResult with per-segment parameters in segments.

Raises: ValueError: If inputs are invalid or segments cannot be fitted.

predict_discharge

predict_discharge(result: RatingCurveResult, stage: ndarray | Series) -> np.ndarray

Predict discharge from stage using a fitted rating curve.

For segmented curves the appropriate segment is selected for each stage value.

Parameters: result: Fitted :class:RatingCurveResult. stage: Stage values to predict at.

Returns: Predicted discharge array.

predict_stage

predict_stage(result: RatingCurveResult, discharge: ndarray | Series) -> np.ndarray

Inverse prediction: compute stage from discharge.

Solves H = (Q / a)^(1/b) + H₀ for the primary (or first) segment. For segmented curves the first segment whose discharge range contains the query value is used.

Parameters: result: Fitted :class:RatingCurveResult. discharge: Discharge values.

Returns: Predicted stage array.

rating_curve_uncertainty

rating_curve_uncertainty(result: RatingCurveResult, stage: ndarray, confidence: float = 0.95) -> tuple[np.ndarray, np.ndarray]

Compute prediction intervals for discharge estimates.

Uses a residual-based approach: the standard error of the residuals is scaled by the appropriate t-distribution quantile.

Parameters: result: Fitted :class:RatingCurveResult. stage: Stage values at which to compute intervals. confidence: Confidence level (default 0.95).

Returns: Tuple of (lower_bound, upper_bound) discharge arrays.

fit_master_recession

fit_master_recession(segments: list[RecessionSegment]) -> RecessionResult

Fit a master recession curve to the identified segments.

Uses least-squares fitting of ln(Q/Q₀) vs time to estimate the recession constant k in the exponential model Q(t) = Q₀·e^(−t/k).

Parameters:

Name Type Description Default
segments list[RecessionSegment]

Recession segments from :func:identify_recessions.

required

Returns:

Name Type Description
A class:`RecessionResult` with the fitted recession constant and
goodness-of-fit metrics.

Raises:

Type Description
ValueError

If no segments are provided.

identify_recessions

identify_recessions(discharge: Series, *, min_length: int = 5, min_decline_pct: float = 0.05) -> list[RecessionSegment]

Identify recession segments in a daily discharge series.

A recession is a continuous period where each day's discharge is less than the previous day's. Very short segments or those with negligible total decline are excluded.

Parameters:

Name Type Description Default
discharge Series

Daily discharge series with a DatetimeIndex.

required
min_length int

Minimum segment length in days.

5
min_decline_pct float

Minimum total decline as a fraction of the starting value.

0.05

Returns:

Type Description
List of :class:`RecessionSegment` instances.

recession_analysis

recession_analysis(discharge: Series, *, min_length: int = 5, min_decline_pct: float = 0.05) -> RecessionResult

Run full recession analysis: identify segments + fit MRC.

Convenience function combining :func:identify_recessions and :func:fit_master_recession.

Parameters:

Name Type Description Default
discharge Series

Daily discharge series with a DatetimeIndex.

required
min_length int

Minimum recession segment length in days.

5
min_decline_pct float

Minimum total decline fraction.

0.05

Returns:

Name Type Description
A class:`RecessionResult`.

baseflow_index_simple

baseflow_index_simple(discharge: Series) -> float

Quick BFI using the Lyne–Hollick 1-pass digital filter.

Uses alpha=0.925 and a single forward pass for speed. For a more robust estimate use :func:aquascope.hydrology.baseflow.lyne_hollick directly with multiple passes.

Parameters: discharge: Daily discharge time series with DatetimeIndex.

Returns: Baseflow index (0–1).

compare_signatures

compare_signatures(sig1: SignatureReport, sig2: SignatureReport) -> dict[str, float]

Compare two signature reports field-by-field.

Parameters: sig1: First :class:SignatureReport. sig2: Second :class:SignatureReport.

Returns: Dictionary of {field_name: absolute_percent_difference} for every numeric field. Fields that are None in either report are skipped.

compute_signatures

compute_signatures(discharge: Series, precipitation: Series | None = None, area_km2: float | None = None) -> SignatureReport

Compute comprehensive hydrological signatures from daily streamflow.

Parameters: discharge: Daily discharge time series (pd.Series with DatetimeIndex). Must contain at least 365 non-NaN values. precipitation: Optional daily precipitation series aligned with discharge. When provided, runoff ratio and elasticity are calculated. area_km2: Optional catchment area in km². Currently reserved for future unit-conversion but not required for any signature.

Returns: :class:SignatureReport with all computed signatures.

Raises: ValueError: If discharge has fewer than 365 non-NaN values.

fdc_slope

fdc_slope(discharge: Series, lower: float = 0.33, upper: float = 0.66) -> float

Slope of the flow duration curve between lower and upper exceedance percentiles.

Calculates the FDC slope in log space per Sawicz et al. (2011):

.. math:: S_{\text{FDC}} = \frac{\ln(Q_{p_1}) - \ln(Q_{p_2})}{p_2 - p_1}

where :math:p_1 (lower, default 0.33) and :math:p_2 (upper, default 0.66) are exceedance probabilities. A steep FDC slope indicates a flashy, rain-dominated catchment, while a flat slope indicates strong storage or groundwater contribution.

Parameters: discharge: Daily discharge time series with DatetimeIndex. lower: Lower exceedance probability (default 0.33). upper: Upper exceedance probability (default 0.66).

Returns: FDC slope (dimensionless, ≥ 0).

flashiness_index

flashiness_index(discharge: Series) -> float

Richards-Baker Flashiness Index.

.. math:: FI = \frac{\sum |Q_i - Q_{i-1}|}{\sum Q_i}

Higher values indicate a more flashy/responsive catchment.

Parameters: discharge: Daily discharge time series with DatetimeIndex.

Returns: Flashiness index (dimensionless, ≥ 0).

flow_elasticity

flow_elasticity(discharge: Series, precipitation: Series) -> float

Sankarasubramanian precipitation-streamflow elasticity.

Computed year-by-year as:

.. math:: E = \text{median}\left(\frac{dQ / \bar{Q}}{dP / \bar{P}}\right)

where dQ and dP are annual departures from the long-term mean. An elasticity > 1 means streamflow is proportionally more variable than precipitation.

Parameters: discharge: Daily discharge time series with DatetimeIndex. precipitation: Daily precipitation with the same DatetimeIndex.

Returns: Elasticity coefficient (dimensionless).

Raises: ValueError: If fewer than 2 complete years are available.

recession_constant

recession_constant(discharge: Series, min_length: int = 5) -> float

Mean recession constant from all recession segments.

A recession segment is a run of consecutive days where discharge decreases. For each segment of at least min_length days the exponential decay rate k is estimated by linear regression of ln(Q) against time. The median k across all segments is returned.

Parameters: discharge: Daily discharge time series with DatetimeIndex. min_length: Minimum number of consecutive falling days to qualify as a recession segment.

Returns: Median recession constant k (day⁻¹, positive).

runoff_ratio

runoff_ratio(discharge: Series, precipitation: Series) -> float

Runoff ratio (total runoff / total precipitation).

Measures the fraction of precipitation that leaves the catchment as streamflow over a shared observation period (Sawicz et al. 2011).

Parameters: discharge: Daily discharge time series with DatetimeIndex. precipitation: Daily precipitation time series with DatetimeIndex.

Returns: Runoff ratio (dimensionless, ≥ 0).

seasonality_index

seasonality_index(discharge: Series) -> tuple[float, int]

Markham's seasonality index and concentration month.

Monthly mean flows are treated as vectors with direction equal to the month's angular position on a unit circle. The resultant's magnitude (normalised) gives the seasonality index and its direction gives the peak month.

Parameters: discharge: Daily discharge time series with DatetimeIndex.

Returns: Tuple of (index, peak_month) where index ranges from 0 (uniform flow throughout the year) to 1 (all flow concentrated in a single month), and peak_month is 1–12.

similarity_score

similarity_score(sig1: SignatureReport, sig2: SignatureReport, weights: dict[str, float] | None = None) -> float

Compute overall similarity between two catchments.

The score is a weighted Euclidean distance in normalised signature space. A score of 0 means the reports are identical; higher values indicate greater dissimilarity.

Parameters: sig1: First :class:SignatureReport. sig2: Second :class:SignatureReport. weights: Optional mapping of field name → weight. Fields not in the dict receive a weight of 1.0. Defaults emphasise BFI, flashiness, seasonality, and runoff ratio.

Returns: Weighted Euclidean distance (≥ 0).

stage_to_runoff

stage_to_runoff(rating: RatingCurveResult, stage: ndarray | Series, catchment_area_km2: float) -> pd.Series

Convert a stage time series to area-normalized daily runoff (mm/day).

Parameters:

Name Type Description Default
rating RatingCurveResult

A fitted rating curve, e.g. from :func:fit_rating_curve or :func:fit_segmented_rating_curve.

required
stage array - like or Series

Stage (water level) values. If a Series with a datetime index is passed, the index is preserved on the output.

required
catchment_area_km2 float

Upstream drainage area in km2. Must be positive.

required

Returns:

Type Description
Series

Runoff in mm/day, same length and index as the input stage series.

Raises:

Type Description
ValueError

If catchment_area_km2 is not positive.


Agricultural water management

FAO-56 Penman–Monteith, crop water requirements, soil water balance.

aquascope.agri

Agricultural water management module.

Implements FAO-56 Penman-Monteith reference evapotranspiration, crop water requirements, and soil water balance modeling.

AgricultureBenchmarkResult dataclass

AgricultureBenchmarkResult(metric_id: str, metric_name: str, output_unit: str, summary: str, table: DataFrame)

Structured result from an AQUASTAT benchmarking workflow.

to_dict

to_dict() -> dict[str, object]

Return a JSON-serializable representation.

IrrigationPlan dataclass

IrrigationPlan(crop: str, planting_date: date, season_end_date: date, efficiency: float, total_eto_mm: float, total_precipitation_mm: float, total_effective_rain_mm: float, total_etc_mm: float, total_net_irrigation_mm: float, total_gross_irrigation_mm: float, total_applied_irrigation_mm: float, irrigation_trigger_days: int, schedule: DataFrame, balance: DataFrame)

Structured result from a crop irrigation planning workflow.

to_dict

to_dict() -> dict[str, object]

Convert the plan to a JSON-serializable dictionary.

WaPORProductivityResult dataclass

WaPORProductivityResult(metric_id: str, metric_name: str, output_unit: str, aggregate_value: float, summary: str, table: DataFrame, aquastat_context: list[AgricultureBenchmarkResult] | None = None)

Structured result from a WaPOR productivity workflow.

to_dict

to_dict() -> dict[str, object]

Return a JSON-serializable representation.

SoilWaterBalance

SoilWaterBalance(soil: SoilProperties, depletion_fraction: float = 0.5, initial_depletion: float = 0.0)

Daily soil water balance tracker.

Parameters:

Name Type Description Default
soil SoilProperties

Soil hydraulic properties.

required
depletion_fraction float

Fraction of TAW that can be depleted before stress (p, default 0.5).

0.5
initial_depletion float

Starting depletion in mm (default 0.0 = field capacity).

0.0
References

Allen et al. (1998), FAO-56 Ch. 8. ISBN 92-5-104219-5.

step

step(etc: float, precipitation: float = 0.0, irrigation: float = 0.0, runoff: float = 0.0) -> SoilWaterStatus

Advance the water balance by one day.

Parameters:

Name Type Description Default
etc float

Crop evapotranspiration (mm/day).

required
precipitation float

Daily precipitation (mm).

0.0
irrigation float

Applied irrigation (mm).

0.0
runoff float

Surface runoff (mm).

0.0

Returns:

Type Description
SoilWaterStatus

Updated soil water status.

run

run(etc_series: Series, precip_series: Series, irrigation_series: Series | None = None) -> pd.DataFrame

Run the water balance over a time series.

Parameters:

Name Type Description Default
etc_series Series

Daily ETc (mm/day) with DatetimeIndex.

required
precip_series Series

Daily precipitation (mm) with DatetimeIndex.

required
irrigation_series Series | None

Daily irrigation (mm). If None, no irrigation is applied.

None

Returns:

Type Description
DataFrame

Daily soil water status records.

auto_irrigate

auto_irrigate(etc_series: Series, precip_series: Series, efficiency: float = 0.7) -> pd.DataFrame

Run balance with automatic irrigation when depletion exceeds RAW.

Parameters:

Name Type Description Default
etc_series Series

Daily ETc (mm/day).

required
precip_series Series

Daily precipitation (mm).

required
efficiency float

Irrigation system efficiency (0-1).

0.7

Returns:

Type Description
DataFrame

Daily water balance with irrigation_mm column.

Notes

irrigation_mm reports the gross water applied (water pumped), accounting for conveyance/application efficiency. Only the net depth - the amount that actually reaches the root zone, i.e. the depletion being refilled - is passed into :meth:step, so conveyance/application losses are not double-counted as deep_percolation_mm.

benchmark_aquastat

benchmark_aquastat(df: DataFrame, metric_id: str, *, year: int | None = None, countries: list[str] | None = None, latest_only: bool = True, top_n: int | None = None) -> AgricultureBenchmarkResult

Compute a country-scale benchmark from AQUASTAT data.

list_benchmark_metrics

list_benchmark_metrics() -> list[str]

Return supported benchmark metric IDs.

crop_water_requirement

crop_water_requirement(eto_series: Series, crop: str, planting_date: date, stage_lengths: dict[str, int] | None = None, method: str = 'single', kc_max: float = 1.2, few: float = 1.0, kr: float = 1.0, *, ground_cover: float | None = None, density: str | None = None, variety: str | None = None, class_name: str | None = None) -> pd.DataFrame

Compute daily crop water requirement over the growing season.

Supports both the FAO-56 single crop coefficient (Kc) and dual crop coefficient (Kcb + Ke) approaches, with optional woody-crop parameterization per FAO-56 Rev.1 (2025).

Parameters:

Name Type Description Default
eto_series Series

Daily reference ET (mm/day) with a DatetimeIndex.

required
crop str

Crop name (key in KC_TABLE / KCB_TABLE).

required
planting_date date

Planting or sowing date.

required
stage_lengths dict[str, int] | None

Days per stage. Defaults to DEFAULT_STAGE_LENGTHS[crop].

None
method str

"single" (default, FAO-56 Ch. 6) or "dual" (FAO-56 Ch. 7). The single method uses Kc directly; the dual method computes ETc = (Kcb + Ke) × ET₀ where Kcb is the basal crop coefficient and Ke is the soil-evaporation coefficient.

'single'
kc_max float

Maximum Kc after rain/irrigation (dual method only, default 1.20). See compute_ke for details.

1.2
few float

Exposed-and-wetted soil fraction (dual method only, default 1.0). See compute_ke for details.

1.0
kr float

Soil evaporation reduction coefficient (dual method only, default 1.0). See compute_ke for details.

1.0
ground_cover float | None

Effective ground cover fraction (0–1), passed to get_kc / get_kcb.

None
density str | None

Planting density or training class, passed to get_kc / get_kcb.

None
variety str | None

Crop variety or sub-species, passed to get_kc / get_kcb.

None
class_name str | None

Explicit class name, passed to get_kc / get_kcb.

None

Returns:

Type Description
DataFrame

Single method columns: date, stage, kc, eto, etc. Dual method adds: kcb, ke, kc_dual.

Raises:

Type Description
ValueError

If method is not "single" or "dual".

References

Allen et al. (1998); Pereira et al. (2025), FAO-56 Ch. 6 (single), Ch. 7 (dual).

get_kc

get_kc(crop: str, stage: str | None = None, *, ground_cover: float | None = None, density: str | None = None, variety: str | None = None, class_name: str | None = None) -> float | dict[str, float]

Get single crop coefficient(s) for a crop from FAO-56 Table 12 / Rev.1 Tables 6.1–6.3.

Parameters:

Name Type Description Default
crop str

Crop name (must be a key in KC_TABLE).

required
stage str | None

Growth stage: "initial", "mid", or "late". If None, returns all coefficients as a dict.

None
ground_cover float | None

Effective ground cover fraction (0–1), used to resolve woody-crop classes (e.g. olive, grape, citrus) per FAO-56 Rev.1 Table 6.3.

None
density str | None

Planting density or training class (e.g. "low", "medium", "high", "super_intensive").

None
variety str | None

Crop variety or sub-species (e.g. "table" / "wine" for grape; "lemon" / "mandarin" for citrus; "durum" for wheat_winter).

None
class_name str | None

Explicit FAO-56 Rev.1 class identifier from WOODY_CROP_KC_CLASSES.

None

Returns:

Type Description
float | dict[str, float]

Kc value for the given stage, or a dict of all stages.

Raises:

Type Description
ValueError

If crop, stage, class_name, variety, or density is unknown.

References

Allen et al. (1998), Table 12; Pereira et al. (2025), FAO-56 Rev.1 Tables 6.1–6.3.

irrigation_schedule

irrigation_schedule(eto_series: Series, precip_series: Series, crop: str, planting_date: date, efficiency: float = 0.7, stage_lengths: dict[str, int] | None = None, method: str = 'single', kc_max: float = 1.2, few: float = 1.0, kr: float = 1.0, *, ground_cover: float | None = None, density: str | None = None, variety: str | None = None, class_name: str | None = None) -> pd.DataFrame

Full irrigation scheduling over the growing season.

Parameters:

Name Type Description Default
eto_series Series

Daily reference ET (mm/day) with a DatetimeIndex.

required
precip_series Series

Daily precipitation (mm) with a DatetimeIndex.

required
crop str

Crop name.

required
planting_date date

Planting date.

required
efficiency float

Irrigation system efficiency (0–1).

0.7
stage_lengths dict[str, int] | None

Days per stage.

None
method str

"single" (default) or "dual". Passed through to crop_water_requirement.

'single'
kc_max float

Maximum Kc after rain/irrigation (dual method only, default 1.20).

1.2
few float

Exposed-and-wetted soil fraction (dual method only, default 1.0).

1.0
kr float

Soil evaporation reduction coefficient (dual method only, default 1.0).

1.0
ground_cover float | None

Passed to crop_water_requirement.

None
density str | None

Passed to crop_water_requirement.

None
variety str | None

Passed to crop_water_requirement.

None
class_name str | None

Passed to crop_water_requirement.

None

Returns:

Type Description
DataFrame

All columns from crop_water_requirement plus effective_rain, net_irrigation, gross_irrigation.

References

Allen et al. (1998); Pereira et al. (2025), FAO-56 Ch. 7.

hargreaves

hargreaves(t_min: float, t_max: float, ra: float) -> float

Hargreaves reference ET₀ estimate (temperature-based).

A simpler alternative when only temperature data is available::

ET₀ = 0.0023 × (T_mean + 17.8) × (T_max − T_min)^0.5 × Ra

Parameters:

Name Type Description Default
t_min float

Minimum daily temperature (°C).

required
t_max float

Maximum daily temperature (°C).

required
ra float

Extraterrestrial radiation (MJ/m²/day). Use extraterrestrial_radiation to compute this.

required

Returns:

Type Description
float

Reference evapotranspiration ET₀ in mm/day.

References

Hargreaves, G. H. & Samani, Z. A. (1985). Reference crop evapotranspiration from temperature. Applied Engineering in Agriculture, 1(2), 96–99.

penman_monteith_daily

penman_monteith_daily(t_min: float, t_max: float, rh_min: float, rh_max: float, u2: float, rs: float, latitude: float, elevation: float, doy: int) -> float

FAO-56 Penman-Monteith daily reference evapotranspiration.

Parameters:

Name Type Description Default
t_min float

Minimum daily temperature (°C).

required
t_max float

Maximum daily temperature (°C).

required
rh_min float

Minimum relative humidity (%).

required
rh_max float

Maximum relative humidity (%).

required
u2 float

Wind speed at 2 m height (m/s).

required
rs float

Incoming solar radiation (MJ/m²/day).

required
latitude float

Latitude in decimal degrees.

required
elevation float

Station elevation above sea level (m).

required
doy int

Day of the year (1–366).

required

Returns:

Type Description
float

Reference evapotranspiration ET₀ in mm/day.

References

Allen et al. (1998), Eq. 6. ISBN 92-5-104219-5.

penman_monteith_series

penman_monteith_series(df: DataFrame, latitude: float, elevation: float) -> pandas.Series

Apply Penman-Monteith ET₀ to each row of a weather DataFrame.

The DataFrame must have a DatetimeIndex and the following columns: t_min, t_max, rh_min, rh_max, wind_speed, solar_radiation.

Parameters:

Name Type Description Default
df DataFrame

Daily weather observations.

required
latitude float

Latitude in decimal degrees.

required
elevation float

Station elevation in metres.

required

Returns:

Type Description
Series

Daily ET₀ values in mm/day, indexed like df.

References

Allen et al. (1998), FAO Irrigation and Drainage Paper 56. ISBN 92-5-104219-5.

default_season_end_date

default_season_end_date(crop: str, planting_date: date, stage_lengths: dict[str, int] | None = None) -> date

Return the default end date for a crop season.

fetch_openmeteo_plan_inputs

fetch_openmeteo_plan_inputs(latitude: float, longitude: float, start_date: str, end_date: str) -> tuple[pd.Series, pd.Series]

Fetch daily ET0 and precipitation from Open-Meteo for planning.

plan_irrigation

plan_irrigation(crop: str, planting_date: date, eto_series: Series, precip_series: Series, soil: SoilProperties, *, efficiency: float = 0.7, depletion_fraction: float = 0.5, initial_depletion: float = 0.0, stage_lengths: dict[str, int] | None = None) -> IrrigationPlan

Build a full irrigation plan from daily ET and precipitation series.

estimate_wapor_productivity

estimate_wapor_productivity(*, metric_id: str, aeti_df: DataFrame | None = None, npp_df: DataFrame | None = None, ret_df: DataFrame | None = None, aquastat_df: DataFrame | None = None, aquastat_metrics: list[str] | None = None, aquastat_year: int | None = None, aquastat_countries: list[str] | None = None, aquastat_top_n: int | None = 10) -> WaPORProductivityResult

Compute a WaPOR productivity or ET performance metric.

list_productivity_metrics

list_productivity_metrics() -> list[str]

Return supported productivity metric IDs.


Visualization

aquascope.viz

AquaScope visualisation module.

Provides publication-quality plots for water-quality analysis, hydrology, forecasting, and spatial data. All functions lazily import matplotlib so the module can be imported even when the viz optional dependency group is not installed — an ImportError is raised only when a plot function is actually called.

Quick start::

from aquascope.viz import plot_timeseries, plot_forecast, plot_fdc

plot_timeseries(df, title="Daily Discharge")
plot_forecast(observed=train, forecast=pred, save_path="forecast.png")
plot_fdc(discharge_series, save_path="fdc.svg")