From e4aae64f218159fa9e842a1697639df5d1439682 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Tue, 11 Aug 2026 19:18:29 +0300 Subject: [PATCH 1/7] validate calibration time heuristics --- src/rtichoke/calibration/__init__.py | 33 +++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/rtichoke/calibration/__init__.py b/src/rtichoke/calibration/__init__.py index 190e74e9..005ad59b 100644 --- a/src/rtichoke/calibration/__init__.py +++ b/src/rtichoke/calibration/__init__.py @@ -2,6 +2,37 @@ Subpackage for Calibration """ -from .calibration import create_calibration_curve, create_calibration_curve_times +from functools import wraps +from inspect import signature + +from .calibration import create_calibration_curve +from .calibration import create_calibration_curve_times as _create_calibration_curve_times + + +@wraps(_create_calibration_curve_times) +def create_calibration_curve_times(*args, **kwargs): + bound = signature(_create_calibration_curve_times).bind_partial(*args, **kwargs) + heuristics_sets = bound.arguments.get("heuristics_sets") + + if heuristics_sets is not None: + unsupported = [ + heuristics + for heuristics in heuristics_sets + if heuristics.get("censoring_heuristic") == "adjusted" + or heuristics.get("competing_heuristic") == "adjusted_as_censored" + ] + if unsupported: + raise ValueError( + "Unsupported calibration heuristics: " + "create_calibration_curve_times() does not support " + "censoring_heuristic='adjusted' or " + "competing_heuristic='adjusted_as_censored'. " + "Use a supported heuristic combination such as " + "censoring_heuristic='excluded' with " + "competing_heuristic='adjusted_as_negative'." + ) + + return _create_calibration_curve_times(*args, **kwargs) + __all__ = ["create_calibration_curve", "create_calibration_curve_times"] From c4f858724ee37c6db8e025e1f6d98cf753204ffb Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Tue, 11 Aug 2026 19:18:43 +0300 Subject: [PATCH 2/7] route calibration through package API --- src/rtichoke/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rtichoke/__init__.py b/src/rtichoke/__init__.py index bc84b742..f489bb15 100644 --- a/src/rtichoke/__init__.py +++ b/src/rtichoke/__init__.py @@ -30,7 +30,7 @@ ) from rtichoke.discrimination.gains import plot_gains_curve as plot_gains_curve -from rtichoke.calibration.calibration import ( +from rtichoke.calibration import ( create_calibration_curve as create_calibration_curve, create_calibration_curve_times as create_calibration_curve_times, ) From 93cc9aed897423b4486018a48e9bf40f1283af4a Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Tue, 11 Aug 2026 19:19:07 +0300 Subject: [PATCH 3/7] test calibration heuristic validation --- tests/test_calibration_times.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_calibration_times.py b/tests/test_calibration_times.py index 8e73c63a..35c7bd70 100644 --- a/tests/test_calibration_times.py +++ b/tests/test_calibration_times.py @@ -1,4 +1,5 @@ import numpy as np +import pytest from rtichoke.calibration import create_calibration_curve_times @@ -53,3 +54,23 @@ def test_create_calibration_curve_times_unequal_size_populations(): ) assert {trace.name for trace in fig.data if trace.name} >= {"Train", "Test"} + + +def test_create_calibration_curve_times_rejects_adjusted_censoring(): + probs = {"model_1": np.array([0.1, 0.2, 0.3, 0.4])} + reals = np.array([0, 1, 0, 1]) + times = np.array([1.0, 2.0, 3.0, 4.0]) + + with pytest.raises(ValueError, match="does not support censoring_heuristic='adjusted'"): + create_calibration_curve_times( + probs, + reals, + times, + fixed_time_horizons=[2.0], + heuristics_sets=[ + { + "censoring_heuristic": "adjusted", + "competing_heuristic": "adjusted_as_negative", + } + ], + ) From a27c7ebcbbc7d804dd9abeda5e911a127bfabab1 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Tue, 11 Aug 2026 21:44:16 +0300 Subject: [PATCH 4/7] Validate calibration heuristics at core API --- src/rtichoke/calibration/calibration.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/rtichoke/calibration/calibration.py b/src/rtichoke/calibration/calibration.py index 2efbf57d..cd2080f4 100644 --- a/src/rtichoke/calibration/calibration.py +++ b/src/rtichoke/calibration/calibration.py @@ -101,6 +101,23 @@ def create_calibration_curve_times( ) -> Figure: """Creates a time-dependent Calibration Curve with a slider for different time horizons.""" + unsupported = [ + heuristics + for heuristics in heuristics_sets + if heuristics.get("censoring_heuristic") == "adjusted" + or heuristics.get("competing_heuristic") == "adjusted_as_censored" + ] + if unsupported: + raise ValueError( + "Unsupported calibration heuristics: " + "create_calibration_curve_times() does not support " + "censoring_heuristic='adjusted' or " + "competing_heuristic='adjusted_as_censored'. " + "Use a supported heuristic combination such as " + "censoring_heuristic='excluded' with " + "competing_heuristic='adjusted_as_negative'." + ) + calibration_curve_list_times = _create_calibration_curve_list_times( probs, reals, From b8a735ea25b0557b9e3738213950893404a2d93b Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Tue, 11 Aug 2026 21:44:18 +0300 Subject: [PATCH 5/7] Export core calibration validation directly --- src/rtichoke/calibration/__init__.py | 33 +--------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/src/rtichoke/calibration/__init__.py b/src/rtichoke/calibration/__init__.py index 005ad59b..190e74e9 100644 --- a/src/rtichoke/calibration/__init__.py +++ b/src/rtichoke/calibration/__init__.py @@ -2,37 +2,6 @@ Subpackage for Calibration """ -from functools import wraps -from inspect import signature - -from .calibration import create_calibration_curve -from .calibration import create_calibration_curve_times as _create_calibration_curve_times - - -@wraps(_create_calibration_curve_times) -def create_calibration_curve_times(*args, **kwargs): - bound = signature(_create_calibration_curve_times).bind_partial(*args, **kwargs) - heuristics_sets = bound.arguments.get("heuristics_sets") - - if heuristics_sets is not None: - unsupported = [ - heuristics - for heuristics in heuristics_sets - if heuristics.get("censoring_heuristic") == "adjusted" - or heuristics.get("competing_heuristic") == "adjusted_as_censored" - ] - if unsupported: - raise ValueError( - "Unsupported calibration heuristics: " - "create_calibration_curve_times() does not support " - "censoring_heuristic='adjusted' or " - "competing_heuristic='adjusted_as_censored'. " - "Use a supported heuristic combination such as " - "censoring_heuristic='excluded' with " - "competing_heuristic='adjusted_as_negative'." - ) - - return _create_calibration_curve_times(*args, **kwargs) - +from .calibration import create_calibration_curve, create_calibration_curve_times __all__ = ["create_calibration_curve", "create_calibration_curve_times"] From d3415c920c6c5bff831ac3aad85f7a021cc32355 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Tue, 11 Aug 2026 21:44:33 +0300 Subject: [PATCH 6/7] Cover both unsupported heuristics and import paths --- tests/test_calibration_times.py | 36 ++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/tests/test_calibration_times.py b/tests/test_calibration_times.py index 35c7bd70..6fbc86ed 100644 --- a/tests/test_calibration_times.py +++ b/tests/test_calibration_times.py @@ -1,6 +1,10 @@ import numpy as np import pytest +from rtichoke import create_calibration_curve_times as create_calibration_curve_times_top_level from rtichoke.calibration import create_calibration_curve_times +from rtichoke.calibration.calibration import ( + create_calibration_curve_times as create_calibration_curve_times_direct, +) def test_create_calibration_curve_times(): @@ -56,21 +60,35 @@ def test_create_calibration_curve_times_unequal_size_populations(): assert {trace.name for trace in fig.data if trace.name} >= {"Train", "Test"} -def test_create_calibration_curve_times_rejects_adjusted_censoring(): +@pytest.mark.parametrize( + "entry_point", + [create_calibration_curve_times_top_level, create_calibration_curve_times_direct], +) +@pytest.mark.parametrize( + "heuristics", + [ + { + "censoring_heuristic": "adjusted", + "competing_heuristic": "adjusted_as_negative", + }, + { + "censoring_heuristic": "excluded", + "competing_heuristic": "adjusted_as_censored", + }, + ], +) +def test_create_calibration_curve_times_rejects_unsupported_heuristics( + entry_point, heuristics +): probs = {"model_1": np.array([0.1, 0.2, 0.3, 0.4])} reals = np.array([0, 1, 0, 1]) times = np.array([1.0, 2.0, 3.0, 4.0]) - with pytest.raises(ValueError, match="does not support censoring_heuristic='adjusted'"): - create_calibration_curve_times( + with pytest.raises(ValueError, match="Unsupported calibration heuristics"): + entry_point( probs, reals, times, fixed_time_horizons=[2.0], - heuristics_sets=[ - { - "censoring_heuristic": "adjusted", - "competing_heuristic": "adjusted_as_negative", - } - ], + heuristics_sets=[heuristics], ) From e402574003fb95205fda7515f3333395d4767d2b Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Wed, 12 Aug 2026 06:54:19 +0300 Subject: [PATCH 7/7] Document unsupported calibration heuristics --- src/rtichoke/calibration/calibration.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rtichoke/calibration/calibration.py b/src/rtichoke/calibration/calibration.py index cd2080f4..c809ed6d 100644 --- a/src/rtichoke/calibration/calibration.py +++ b/src/rtichoke/calibration/calibration.py @@ -99,7 +99,12 @@ def create_calibration_curve_times( "#585123", ], ) -> Figure: - """Creates a time-dependent Calibration Curve with a slider for different time horizons.""" + """Create a time-dependent calibration curve across fixed horizons. + + Raises: + ValueError: If a heuristic set requests adjusted censoring or treats + competing events as censored, which calibration does not support. + """ unsupported = [ heuristics