Skip to content

Commit 2560977

Browse files
martin-springercdelinemdeceglieCopilot
authored
Increase test coverage (#486)
* add keyword 'label' to degradation_timeseries_plot, enabling 'left' and 'center' labeling options. * Update changelog, add pytests, update sphinx documentation * fix flake8 grumbles * update pytests to include axes limits * fix flake8 grumbles * add 'label' input option to `degradation_year_on_year`. Fixes #459 * add pytests and update changelog. * flake8 grumbles * Minor updates to setup.py (constrain scipy<1.16) and refactor degradation_test * Custom fix for Pandas < 2.0.0 which can't average two columns of timestamps. * flake8 grumbles * keep TZ-aware timestamps. Update pytests to specifically test _avg_timestamp_old_Pandas * flake8 grumbles * try to UTC localize the pytest... * Add .asfreq() to get pytests to agree * switch to calendar.timegm to hopefully remove TZ issues.. * regardless of uncertainty_method, return calc_info{'YoY_values') * update _right dt labels to correct _left labels in degradation_year_on_year * update _avg_timestamp_old_Pandas to allow for numeric index instead of timestamp * add left label option to degradation_year_on_year * update degradation_year_on_year, index set to either left, center or right. Consistent with #394 - multi_yoy * update return for default = none uncertainty option * degradation_year_on_year - go back to single return when uncertainty_value = None to avoid breaking pytests. * add multi-year aggregation of slopes in degradation_year_on_year * add multi_yoy kwarg in degradation_year_on_year to toggle the multi-YoY function. * update plotting for detailed=True, allow usage_of_points > 2 * flake8 grumbles * update plotting detailed=True for (even) and (odd) number of points coloring * To allow multi_yoy=True in plotting.degradation_timeseries_plot, resample.mean() the YoY_values. * flake8 grumbles * Add warning to degradation_timeseries_plot when multi_YoY=True * update to warning message in plotting.degradation_timeseries_plot * fix flake8 grumbles * nbval fixes from qnguyen345-bare_except_error * Add pandas 3.0 futurewarning handling * Try again to solve pandas3.0 futurewarning * attempt 3 to fix nbval * Add infer_objects to remove futurewarning * minor inline comment update * update plotting tests to be relative value, update ordering of module import in plotting.py, per Copilot review. * update inline comments and whatsnew docs * Clean up inline comments per Copilot review * added multi-YoY pytest - still need to catch warnings * Add a warnings.catch_warnings to the plotting pytest * flake8 grumbles * add multi-YoY=True pytest * updated changelog * update changelog * implement copilot suggestions * linting * use s instead of ns for pandas 3 compatibility * update pandas version comparison * set matplotlib non-gui backend for tests * set dtype resolution based on pandas version * linting import order * boost degradation.py test coverage * add tests for error handling in analysis_chain * update changelog * update changelog * exclude coverage reports with suffixes * Update rdtools/degradation.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * remove label=None handling, rely on default 'right' behavior * refactor dt_center tz handling for old pandas * simplify _avg_timestamp_old_Pandas * degradation_timeseries_plot: change rolling median min_periods to rolling_days / 4. * remove degradation_timeseries_plot(label=) and just default to center=True * update sensor_analysis() and clearsky_analysis() docstrings to discuss passing `label=right` kwargs * flake8 updates * Initial commit - multi-YoY notebook * pretty-print notebook dataframes with tabulate. * update notebook requirements to silence pandas warnings * add multi-yoy nb to tutorials * fix nblink path * Change the yoy_values index to be named 'dt'. Add new illustrations at the end of the multi-YoY notebook. * clean up pending changelog --------- Co-authored-by: cdeline <chris.deline@nrel.gov> Co-authored-by: Michael Deceglie <mdeceglie@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 0294a7e commit 2560977

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

docs/sphinx/source/changelog/pending.rst

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Enhancements
88
to return the calc_info['YoY_values'] as either right labeled (default), left or
99
center labeled. (:issue:`459`)
1010
* :py:func:`~rdtools.plotting.degradation_timeseries_plot` now defaults to rolling
11-
median, centered on the timestamp (pd.rolling(center=True)).
11+
median, centered on the timestamp (pd.rolling(center=True)), and reduces
12+
``min_periods`` from ``rolling_days//2`` to ``rolling_days//4``.
1213
(:issue:`455`)
1314
* :py:func:`~rdtools.degradation.degradation_year_on_year` has new parameter ``multi_yoy``
1415
(default False) to trigger multiple YoY degradation calculations similar to Hugo Quest et
@@ -27,6 +28,9 @@ Enhancements
2728
* Added new example notebook ``docs/Multi-year_on_year_example.ipynb`` demonstrating the
2829
``label='center'`` and ``multi_yoy=True`` features of
2930
:py:func:`~rdtools.degradation.degradation_year_on_year`. (:issue:`394`)
31+
* :py:meth:`~rdtools.analysis_chains.TrendAnalysis.sensor_analysis` and
32+
:py:meth:`~rdtools.analysis_chains.TrendAnalysis.clearsky_analysis` now
33+
explicitly default ``yoy_kwargs={"label": "right"}``.
3034

3135
Bug Fixes
3236
---------
@@ -43,14 +47,24 @@ Maintenance
4347
* Updated ``docs/notebook_requirements.txt`` to require ``numexpr>=2.10.2`` and
4448
``tabulate>=0.9.0`` to satisfy pandas' optional dependency minimum versions and
4549
avoid related warnings/errors.
50+
* Removed trailing semicolons in example notebooks.
51+
* Added ``.coverage.*`` pattern to ``.gitignore``.
4652

4753
Testing
4854
-------
55+
* Added tests for error handling paths in :py:mod:`~rdtools.analysis_chains`:
56+
``filter_params`` and ``filter_params_aggregated`` setter validation,
57+
``clearsky_rescale_index_mismatch``, ``poa_filter_without_poa``,
58+
``tcell_filter_without_temperature``, ``hour_angle_filter_without_location``,
59+
``clearsky_filter_without_poa``, and ``degradation_timeseries_plot_invalid_case``.
4960
* Added tests for error handling paths in :py:mod:`~rdtools.degradation`:
5061
``classical_decomposition`` missing/irregular data, ``year_on_year`` circular block
5162
validation, no valid pairs error, and ``_mk_test`` edge cases (no trend, ties,
5263
decreasing).
5364
* Added test for ``multi_yoy=True`` parameter in ``degradation_year_on_year``.
65+
* Added tests for :py:func:`~rdtools.plotting.degradation_timeseries_plot`
66+
covering ``label='center'``, ``label='left'``, multi-YoY duplicate index
67+
handling, and ``KeyError`` path.
5468
* Set matplotlib backend to ``Agg`` in test ``conftest.py`` to avoid tkinter issues.
5569

5670

rdtools/test/analysis_chains_test.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,3 +1018,90 @@ def test_invalid_filter_params_aggregated(
10181018
KeyError, match=f"Key '{filter_param_aggregated}' is not a valid filter parameter."
10191019
):
10201020
sensor_analysis.filter_params_aggregated[filter_param_aggregated] = {}
1021+
1022+
1023+
def test_filter_params_setter_non_dict(sensor_parameters):
1024+
"""Test that filter_params setter raises error for non-dict input."""
1025+
rd_analysis = TrendAnalysis(**sensor_parameters)
1026+
with pytest.raises(ValueError, match="must be a dictionary"):
1027+
rd_analysis.filter_params = "not a dict"
1028+
1029+
1030+
def test_filter_params_aggregated_setter_non_dict(sensor_parameters):
1031+
"""Test that filter_params_aggregated setter raises error for non-dict."""
1032+
rd_analysis = TrendAnalysis(**sensor_parameters)
1033+
with pytest.raises(ValueError, match="must be a dictionary"):
1034+
rd_analysis.filter_params_aggregated = "not a dict"
1035+
1036+
1037+
def test_clearsky_rescale_index_mismatch(sensor_parameters, cs_input):
1038+
"""Test that rescale=True raises error when indices don't match."""
1039+
rd_analysis = TrendAnalysis(**sensor_parameters)
1040+
rd_analysis.set_clearsky(**cs_input)
1041+
1042+
# Create explicit times that don't match the poa_global index
1043+
mismatched_times = pd.date_range(
1044+
"2020-01-01", periods=100, freq="h", tz=cs_input["pvlib_location"].tz
1045+
)
1046+
1047+
with pytest.raises(ValueError, match="rescale=True can only be used"):
1048+
rd_analysis._calc_clearsky_poa(times=mismatched_times, rescale=True)
1049+
1050+
1051+
def test_poa_filter_without_poa(sensor_parameters):
1052+
"""Test that poa_filter raises error when poa is not available."""
1053+
params = sensor_parameters.copy()
1054+
rd_analysis = TrendAnalysis(**params)
1055+
rd_analysis.filter_params = {"poa_filter": {}}
1056+
# Set poa_global to None after initialization
1057+
rd_analysis.poa_global = None
1058+
# Need power_expected to get past other checks
1059+
rd_analysis.power_expected = sensor_parameters["pv"]
1060+
1061+
with pytest.raises(ValueError, match="poa_global must be available"):
1062+
rd_analysis.sensor_analysis()
1063+
1064+
1065+
def test_tcell_filter_without_temperature(sensor_parameters):
1066+
"""Test that tcell_filter raises error when cell temp not available."""
1067+
params = sensor_parameters.copy()
1068+
params["temperature_ambient"] = None
1069+
params["temperature_cell"] = None
1070+
rd_analysis = TrendAnalysis(**params)
1071+
rd_analysis.poa_global = sensor_parameters["poa_global"]
1072+
rd_analysis.filter_params = {"tcell_filter": {}}
1073+
1074+
# Need power_expected to skip thermal calculation
1075+
rd_analysis.power_expected = sensor_parameters["pv"]
1076+
1077+
with pytest.raises(ValueError, match="Cell temperature must be available"):
1078+
rd_analysis.sensor_analysis()
1079+
1080+
1081+
def test_hour_angle_filter_without_location(sensor_parameters):
1082+
"""Test hour_angle_filter raises error without pvlib_location."""
1083+
rd_analysis = TrendAnalysis(**sensor_parameters)
1084+
rd_analysis.filter_params = {"hour_angle_filter": {}}
1085+
1086+
with pytest.raises(ValueError, match="pvlib location must be provided"):
1087+
rd_analysis.sensor_analysis()
1088+
1089+
1090+
def test_clearsky_filter_without_poa(sensor_parameters, cs_input):
1091+
"""Test clearsky_filter raises error without required poa data."""
1092+
rd_analysis = TrendAnalysis(**sensor_parameters)
1093+
rd_analysis.set_clearsky(**cs_input)
1094+
1095+
# Store the pv_energy for filtering, then set poa_global to None
1096+
energy_normalized = rd_analysis.pv_energy.copy()
1097+
rd_analysis.poa_global = None
1098+
rd_analysis.filter_params = {"clearsky_filter": {}}
1099+
1100+
with pytest.raises(ValueError, match="poa_global and poa_global_clearsky"):
1101+
rd_analysis._filter(energy_normalized, "clearsky")
1102+
1103+
1104+
def test_degradation_timeseries_plot_invalid_case(sensor_analysis):
1105+
"""Test plot_degradation_timeseries raises error for invalid case."""
1106+
with pytest.raises(ValueError, match="case must be either"):
1107+
sensor_analysis.plot_degradation_timeseries(case="invalid")

0 commit comments

Comments
 (0)