processing.shape_check.check_forecast_shape

processing.shape_check.check_forecast_shape(
    y,
    reference,
    *,
    min_corr=0.6,
    min_range_ratio=0.5,
    min_overlap=12,
)

Measure correlation and daily-range ratio between a forecast and its reference.

Ports the plausibility metrics from the operational warn_if_implausible_shape (script lines ~951-965): Pearson correlation on the index intersection and the ratio of forecast range to reference range. A zero-reference range produces float('nan') for range_ratio (zero-range guard).

This function is pure: it performs no logging, no warning emission, and no raising on an implausible result. Callers receive a ShapeCheckReport and decide how to act. Only invalid inputs (non-Series arguments, empty series) raise.

Parameters

Name Type Description Default
y pd.Series Forecast series (e.g. the 24-h submission). required
reference pd.Series Reference profile to compare against (e.g. ENTSO-E day-ahead forecast or actuals one week earlier). required
min_corr float Correlation threshold for ShapeCheckReport.plausible. Default 0.6 matches the operational script. 0.6
min_range_ratio float Range-ratio threshold for ShapeCheckReport.plausible. Default 0.5 matches the operational script. 0.5
min_overlap int Minimum overlap length required to evaluate the metrics. Below this, corr and range_ratio are float('nan') and ShapeCheckReport.skipped returns True. 12

Returns

Name Type Description
ShapeCheckReport ShapeCheckReport with the computed metrics and the passed thresholds.

Raises

Name Type Description
TypeError When y or reference is not a pd.Series.
ValueError When y or reference is empty.

Examples

import pandas as pd
import numpy as np
from spotforecast2_safe.processing.shape_check import check_forecast_shape

idx = pd.date_range("2026-06-11 00:00", periods=24, freq="h", tz="UTC")
profile = pd.Series([float(i % 12) for i in range(24)], index=idx)

# Identical profile -> correlation 1.0, range ratio 1.0, plausible.
report = check_forecast_shape(profile, profile)
print(f"corr={report.corr:.2f}  range_ratio={report.range_ratio:.2f}  "
      f"plausible={report.plausible}")
assert report.plausible

# Flat forecast -> range_ratio < 0.5, not plausible.
flat = pd.Series(5.0, index=idx)
report_flat = check_forecast_shape(flat, profile)
print(f"flat: range_ratio={report_flat.range_ratio:.2f}  "
      f"plausible={report_flat.plausible}")
assert not report_flat.plausible

# Short overlap -> skipped.
short = profile.iloc[:5]
report_short = check_forecast_shape(short, profile, min_overlap=12)
print(f"short: skipped={report_short.skipped}")
assert report_short.skipped
corr=1.00  range_ratio=1.00  plausible=True
flat: range_ratio=0.00  plausible=False
short: skipped=True
/Users/bartz/workspace/spotforecast2-safe/.venv/lib/python3.13/site-packages/numpy/lib/_function_base_impl.py:3023: RuntimeWarning: invalid value encountered in divide
  c /= stddev[:, None]
/Users/bartz/workspace/spotforecast2-safe/.venv/lib/python3.13/site-packages/numpy/lib/_function_base_impl.py:3024: RuntimeWarning: invalid value encountered in divide
  c /= stddev[None, :]