From c4eee738c2eb0a61819c82981b819d595481ae4c Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Fri, 21 Aug 2026 03:45:57 +0300 Subject: [PATCH 1/5] Add calibration browser proof helper --- src/rtichoke/_viz_browser.py | 95 +++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/src/rtichoke/_viz_browser.py b/src/rtichoke/_viz_browser.py index ba154dbd..d76bd362 100644 --- a/src/rtichoke/_viz_browser.py +++ b/src/rtichoke/_viz_browser.py @@ -5,6 +5,7 @@ import json from importlib.resources import files from pathlib import Path +from typing import Any import polars as pl @@ -16,6 +17,16 @@ } +def _copy_viz_assets(output_dir: Path) -> None: + vendor = files("rtichoke").joinpath("_vendor", "rtichoke_viz") + (output_dir / "rtichoke-viz.js").write_bytes( + vendor.joinpath("rtichoke-viz.js").read_bytes() + ) + (output_dir / "rtichoke-viz.css").write_bytes( + vendor.joinpath("rtichoke-viz.css").read_bytes() + ) + + def _roc_spec_from_performance_data( performance_data: pl.DataFrame, ) -> dict[str, object]: @@ -52,6 +63,48 @@ def _roc_spec_from_performance_data( } +def _calibration_spec_from_curve_list( + calibration_curve_list: dict[str, Any], +) -> dict[str, object]: + """Map existing discrete calibration output to the canonical spec.""" + rows = calibration_curve_list["deciles_dat"].select( + "reference_group", "x", "y", "n_reals", "n" + ) + distribution_rows = calibration_curve_list["histogram_for_calibration"].select( + "reference_group", "mids", "counts" + ) + + return { + "schemaVersion": "1.0", + "type": "calibration", + "data": [ + { + "model": row["reference_group"], + "predicted": row["x"], + "observed": row["y"], + "method": "discrete", + "events": row["n_reals"], + "total": row["n"], + } + for row in rows.to_dicts() + ], + "distribution": [ + { + "model": row["reference_group"], + "midpoint": row["mids"], + "count": row["counts"], + "binWidth": 0.01, + } + for row in distribution_rows.to_dicts() + ], + "x": "predicted", + "y": "observed", + "xAxis": {"label": "Predicted probability", "domain": [0, 1]}, + "yAxis": {"label": "Observed probability", "domain": [0, 1]}, + "references": [{"type": "identity"}], + } + + def _write_roc_browser_html( performance_data: pl.DataFrame, output_path: str | Path, @@ -59,12 +112,7 @@ def _write_roc_browser_html( """Write a standalone proof page that uses the vendored browser renderer.""" output = Path(output_path) output.parent.mkdir(parents=True, exist_ok=True) - - vendor = files("rtichoke").joinpath("_vendor", "rtichoke_viz") - js_source = vendor.joinpath("rtichoke-viz.js") - css_source = vendor.joinpath("rtichoke-viz.css") - (output.parent / "rtichoke-viz.js").write_bytes(js_source.read_bytes()) - (output.parent / "rtichoke-viz.css").write_bytes(css_source.read_bytes()) + _copy_viz_assets(output.parent) spec_json = json.dumps(_roc_spec_from_performance_data(performance_data)).replace( " Path: + """Write a standalone discrete-calibration proof using vendored assets.""" + output = Path(output_path) + output.parent.mkdir(parents=True, exist_ok=True) + _copy_viz_assets(output.parent) + + spec_json = json.dumps(_calibration_spec_from_curve_list(calibration_curve_list)).replace( + " + + + + + rtichoke_viz calibration vendoring proof + + + +
+ + + + +""" + output.write_text(html, encoding="utf-8") + return output From 82f4e0da14c776be16afa1ac090341537ec69a6f Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Fri, 21 Aug 2026 03:46:05 +0300 Subject: [PATCH 2/5] Add calibration browser proof example --- examples/rtichoke_viz_calibration.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 examples/rtichoke_viz_calibration.py diff --git a/examples/rtichoke_viz_calibration.py b/examples/rtichoke_viz_calibration.py new file mode 100644 index 00000000..15f9c7ff --- /dev/null +++ b/examples/rtichoke_viz_calibration.py @@ -0,0 +1,22 @@ +"""Generate a browser-rendered calibration proof from real rtichoke output.""" + +from pathlib import Path + +import numpy as np + +from rtichoke._viz_browser import _write_calibration_browser_html +from rtichoke.calibration.calibration import _create_calibration_curve_list + +probs = { + "Model A": np.array( + [0.03, 0.08, 0.12, 0.18, 0.25, 0.32, 0.40, 0.50, 0.62, 0.75, 0.88, 0.96] + ) +} +reals = np.array([0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1]) + +calibration_curve_list = _create_calibration_curve_list(probs, reals) +output = _write_calibration_browser_html( + calibration_curve_list, + Path("rtichoke_viz_calibration_proof") / "index.html", +) +print(output) From 8b48ccfada8a8e91a2c6f714fabc024a966b9faf Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Fri, 21 Aug 2026 03:46:15 +0300 Subject: [PATCH 3/5] Test calibration browser proof --- tests/test_viz_browser.py | 43 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/test_viz_browser.py b/tests/test_viz_browser.py index 79bec8e5..8e23e52c 100644 --- a/tests/test_viz_browser.py +++ b/tests/test_viz_browser.py @@ -3,9 +3,12 @@ import numpy as np from rtichoke._viz_browser import ( + _calibration_spec_from_curve_list, _roc_spec_from_performance_data, + _write_calibration_browser_html, _write_roc_browser_html, ) +from rtichoke.calibration.calibration import _create_calibration_curve_list from rtichoke.performance_data.performance_data import prepare_performance_data @@ -17,6 +20,17 @@ def _real_roc_performance_data(): ) +def _real_calibration_curve_list(): + return _create_calibration_curve_list( + probs={ + "Model A": np.array( + [0.03, 0.08, 0.12, 0.18, 0.25, 0.32, 0.40, 0.50, 0.62, 0.75, 0.88, 0.96] + ) + }, + reals=np.array([0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1]), + ) + + def test_real_roc_output_maps_to_canonical_spec(): performance_data = _real_roc_performance_data() @@ -31,7 +45,21 @@ def test_real_roc_output_maps_to_canonical_spec(): assert all(0 <= row["specificity"] <= 1 for row in spec["data"]) -def test_browser_proof_uses_vendored_assets(tmp_path: Path): +def test_real_calibration_output_maps_to_canonical_spec(): + spec = _calibration_spec_from_curve_list(_real_calibration_curve_list()) + + assert spec["schemaVersion"] == "1.0" + assert spec["type"] == "calibration" + assert spec["references"] == [{"type": "identity"}] + assert spec["data"] + assert {row["model"] for row in spec["data"]} == {"Model A"} + assert all(row["method"] == "discrete" for row in spec["data"]) + assert all(0 <= row["predicted"] <= 1 for row in spec["data"]) + assert all(0 <= row["observed"] <= 1 for row in spec["data"]) + assert spec["distribution"] + + +def test_roc_browser_proof_uses_vendored_assets(tmp_path: Path): output = _write_roc_browser_html( _real_roc_performance_data(), tmp_path / "index.html", @@ -42,3 +70,16 @@ def test_browser_proof_uses_vendored_assets(tmp_path: Path): assert '"type": "roc"' in html assert (tmp_path / "rtichoke-viz.js").stat().st_size > 0 assert (tmp_path / "rtichoke-viz.css").stat().st_size > 0 + + +def test_calibration_browser_proof_uses_vendored_assets(tmp_path: Path): + output = _write_calibration_browser_html( + _real_calibration_curve_list(), + tmp_path / "index.html", + ) + + html = output.read_text(encoding="utf-8") + assert 'import { renderCalibration } from "./rtichoke-viz.js"' in html + assert '"type": "calibration"' in html + assert (tmp_path / "rtichoke-viz.js").stat().st_size > 0 + assert (tmp_path / "rtichoke-viz.css").stat().st_size > 0 From 8a62577a951e65848a32d67d16cb522f9a3962e2 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Fri, 21 Aug 2026 03:46:27 +0300 Subject: [PATCH 4/5] Publish calibration browser proof preview --- .github/workflows/docs.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e3937211..fbcb937a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -93,7 +93,7 @@ jobs: grep -q '.Reactable {' performance-table-reactable.html grep -q 'Real Positive' performance-table-reactable.html mv performance-table-reactable.html great-docs/_site/performance-table-reactable.html - cp -R examples/performance_table_reactable_files great-docs/_site/performance-table-reactable_files + cp -R examples/performance_table_reactable_files great-docs/_site/performance_table_reactable_files - name: Generate rtichoke_viz ROC proof if: github.event.action != 'closed' @@ -102,6 +102,13 @@ jobs: mkdir -p great-docs/_site/rtichoke-viz-roc cp -R rtichoke_viz_roc_proof/. great-docs/_site/rtichoke-viz-roc/ + - name: Generate rtichoke_viz calibration proof + if: github.event.action != 'closed' + run: | + uv run python examples/rtichoke_viz_calibration.py + mkdir -p great-docs/_site/rtichoke-viz-calibration + cp -R rtichoke_viz_calibration_proof/. great-docs/_site/rtichoke-viz-calibration/ + - name: Deploy PR preview uses: rossjrw/pr-preview-action@v1 with: From f70a44b223402f94ef24eff432d66a77ae9835b5 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Fri, 21 Aug 2026 03:50:18 +0300 Subject: [PATCH 5/5] Format calibration browser proof --- src/rtichoke/_viz_browser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rtichoke/_viz_browser.py b/src/rtichoke/_viz_browser.py index d76bd362..a5db2ee8 100644 --- a/src/rtichoke/_viz_browser.py +++ b/src/rtichoke/_viz_browser.py @@ -149,9 +149,9 @@ def _write_calibration_browser_html( output.parent.mkdir(parents=True, exist_ok=True) _copy_viz_assets(output.parent) - spec_json = json.dumps(_calibration_spec_from_curve_list(calibration_curve_list)).replace( - "