processing.shape_check.check_forecast_level

processing.shape_check.check_forecast_level(
    y,
    reference,
    *,
    statistic='median',
    tol=0.02,
    min_overlap=12,
)

Measure the systematic level offset between a forecast and its reference.

Complements check_forecast_shape: a forecast can track the daily profile perfectly (high correlation, good range ratio) yet sit at the wrong level — a flat over- or under-prediction. This returns the signed offset of the forecast’s central level against the reference’s, in absolute and relative terms, and flags it as biased when the relative offset exceeds tol.

Like check_forecast_shape, this function is pure: no logging, no warning, no raising on a biased result. Only invalid inputs raise.

Parameters

Name Type Description Default
y pd.Series Forecast series (e.g. the 24-h submission). required
reference pd.Series Reference profile (e.g. ENTSO-E day-ahead forecast or actuals one week earlier). required
statistic str Central-tendency statistic, "median" (default, robust) or "mean". 'median'
tol float Relative-offset tolerance for LevelCheckReport.biased. Default 0.02 (2 %). 0.02
min_overlap int Minimum overlap length to evaluate. Below this the report is skipped with NaN levels. 12

Returns

Name Type Description
LevelCheckReport LevelCheckReport with the computed levels, offsets, and tol.

Raises

Name Type Description
TypeError When y or reference is not a pd.Series.
ValueError When y or reference is empty, or statistic is not "median" / "mean".

Examples

import pandas as pd
from spotforecast2_safe.processing.shape_check import check_forecast_level

idx = pd.date_range("2026-06-13 00:00", periods=24, freq="h", tz="UTC")
actual = pd.Series([43_000.0 + 3_000.0 * (i % 12) / 12 for i in range(24)],
                   index=idx)

# Forecast that sits a flat 1_800 MW too high -> biased.
high = actual + 1_800.0
rep = check_forecast_level(high, actual, tol=0.02)
print(f"offset={rep.offset:.0f} MW  rel={rep.rel_offset:.3f}  biased={rep.biased}")
assert rep.biased and rep.offset > 0

# A well-centred forecast -> not biased.
ok = actual + 50.0
print("small offset biased:", check_forecast_level(ok, actual).biased)
offset=1800 MW  rel=0.041  biased=True
small offset biased: False