diff --git a/src/rtichoke/_renderers.py b/src/rtichoke/_renderers.py index 712d942e..97135714 100644 --- a/src/rtichoke/_renderers.py +++ b/src/rtichoke/_renderers.py @@ -303,7 +303,5 @@ def _render_lift_v2( if selected == "matplotlib": return _render_lift_matplotlib(spec, size=size, color_values=color_values) if selected in {"browser", "rtichoke_viz"}: - raise ValueError( - "Browser rendering for Lift curves requires a newer vendored release of rtichoke_viz containing Lift support." - ) + return RtichokeBrowserChart(spec=spec, size=size) raise ValueError("The Plotly renderer must use the existing production path.") diff --git a/src/rtichoke/_vendor/rtichoke_viz/README b/src/rtichoke/_vendor/rtichoke_viz/README index 467f5473..f6614cd5 100644 --- a/src/rtichoke/_vendor/rtichoke_viz/README +++ b/src/rtichoke/_vendor/rtichoke_viz/README @@ -1,3 +1,3 @@ Vendored from the immutable rtichoke_viz release recorded in VENDORED_FROM. The JS/CSS and both schema files are copied byte-for-byte from that release archive. -Production browser integration remains on the existing v1 spec until a separate adoption change. +Browser integrations use only renderer exports present in the recorded immutable release. diff --git a/src/rtichoke/_vendor/rtichoke_viz/VENDORED_FROM b/src/rtichoke/_vendor/rtichoke_viz/VENDORED_FROM index 7f2d8c53..a53a2986 100644 --- a/src/rtichoke/_vendor/rtichoke_viz/VENDORED_FROM +++ b/src/rtichoke/_vendor/rtichoke_viz/VENDORED_FROM @@ -1,6 +1,5 @@ repository=https://github.com/uriahf/rtichoke_viz -release=v0.3.1 -source_commit=5ccde928a0bf9fa6ece2b7572687b442c57a98a9 -archive=rtichoke-viz-0.3.1.tar.gz -sha256=121aa8eb8d0f8427ecfb2c01dab0fb05668eaedf47ddcfc0cd282a7ecf1ce448 - +release=v0.4.0 +source_commit=2125c7099839cadd536c6f38f3f7e23a17ca4348 +archive=rtichoke-viz-0.4.0.tar.gz +sha256=9a687cd938f1875d577e592664ca75447455166169f6132dd7f79406515e14e1 diff --git a/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-0.3.1.tar.gz b/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-0.3.1.tar.gz deleted file mode 100644 index ddaf5fa6..00000000 Binary files a/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-0.3.1.tar.gz and /dev/null differ diff --git a/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-0.4.0.tar.gz b/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-0.4.0.tar.gz new file mode 100644 index 00000000..e9560d61 Binary files /dev/null and b/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-0.4.0.tar.gz differ diff --git a/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-v2.schema.json b/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-v2.schema.json index 95a21a72..35b09dd4 100644 --- a/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-v2.schema.json +++ b/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-v2.schema.json @@ -2265,6 +2265,558 @@ } } ] + }, + { + "type": "object", + "allOf": [ + { + "type": "object", + "required": [ + "schemaVersion", + "evaluations", + "series", + "xAxis", + "yAxis" + ], + "properties": { + "schemaVersion": { + "const": "2.0", + "type": "string" + }, + "title": { + "type": "string" + }, + "evaluations": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "population" + ], + "properties": { + "id": { + "type": "string" + }, + "model": { + "type": "string" + }, + "population": { + "type": "string" + }, + "label": { + "type": "string" + } + } + } + }, + "series": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "evaluationId", + "display" + ], + "properties": { + "id": { + "type": "string" + }, + "evaluationId": { + "type": "string" + }, + "horizon": { + "minimum": 0, + "type": "number" + }, + "display": { + "type": "object", + "required": [ + "label", + "group", + "role" + ], + "properties": { + "label": { + "type": "string" + }, + "group": { + "type": "string" + }, + "role": { + "anyOf": [ + { + "const": "model", + "type": "string" + }, + { + "const": "population", + "type": "string" + }, + { + "const": "evaluation", + "type": "string" + }, + { + "const": "context", + "type": "string" + } + ] + } + } + } + } + } + }, + "xAxis": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + }, + "domain": { + "type": "array", + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "additionalItems": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "yAxis": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + }, + "domain": { + "type": "array", + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "additionalItems": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "references": { + "type": "array", + "items": { + "anyOf": [ + { + "allOf": [ + { + "anyOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "const": "identity", + "type": "string" + }, + "label": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "horizontal", + "type": "string" + }, + "value": { + "type": "number" + }, + "label": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "vertical", + "type": "string" + }, + "value": { + "type": "number" + }, + "label": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "points" + ], + "properties": { + "type": { + "const": "path", + "type": "string" + }, + "points": { + "minItems": 2, + "type": "array", + "items": { + "type": "object", + "required": [ + "x", + "y" + ], + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + } + } + }, + "label": { + "type": "string" + } + } + } + ] + }, + { + "type": "object", + "required": [ + "scope" + ], + "properties": { + "scope": { + "const": "global", + "type": "string" + } + } + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "const": "identity", + "type": "string" + }, + "label": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "horizontal", + "type": "string" + }, + "value": { + "type": "number" + }, + "label": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "vertical", + "type": "string" + }, + "value": { + "type": "number" + }, + "label": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "points" + ], + "properties": { + "type": { + "const": "path", + "type": "string" + }, + "points": { + "minItems": 2, + "type": "array", + "items": { + "type": "object", + "required": [ + "x", + "y" + ], + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + } + } + }, + "label": { + "type": "string" + } + } + } + ] + }, + { + "type": "object", + "required": [ + "scope", + "population" + ], + "properties": { + "scope": { + "const": "population", + "type": "string" + }, + "population": { + "type": "string" + } + } + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "const": "identity", + "type": "string" + }, + "label": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "horizontal", + "type": "string" + }, + "value": { + "type": "number" + }, + "label": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "vertical", + "type": "string" + }, + "value": { + "type": "number" + }, + "label": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "points" + ], + "properties": { + "type": { + "const": "path", + "type": "string" + }, + "points": { + "minItems": 2, + "type": "array", + "items": { + "type": "object", + "required": [ + "x", + "y" + ], + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + } + } + }, + "label": { + "type": "string" + } + } + } + ] + }, + { + "type": "object", + "required": [ + "scope", + "population", + "horizon" + ], + "properties": { + "scope": { + "const": "population_horizon", + "type": "string" + }, + "population": { + "type": "string" + }, + "horizon": { + "minimum": 0, + "type": "number" + } + } + } + ] + } + ] + } + } + } + }, + { + "type": "object", + "required": [ + "type", + "data", + "x", + "y" + ], + "properties": { + "type": { + "const": "lift", + "type": "string" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "seriesId", + "cutoff", + "ppcr", + "lift" + ], + "properties": { + "seriesId": { + "type": "string" + }, + "cutoff": { + "type": "number" + }, + "ppcr": { + "minimum": 0, + "maximum": 1, + "type": "number" + }, + "lift": { + "type": "number" + } + } + } + }, + "x": { + "const": "ppcr", + "type": "string" + }, + "y": { + "const": "lift", + "type": "string" + } + } + } + ] } ] } diff --git a/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz.js b/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz.js index 8f4f41d0..704fdb12 100644 --- a/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz.js +++ b/src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz.js @@ -2817,6 +2817,23 @@ var GainsV2SpecSchema = Type.Intersect([ }) ]); +// src/spec/v2/lift.ts +var LiftV2DatumSchema = Type.Object({ + seriesId: Type.String(), + cutoff: Type.Number(), + ppcr: Type.Number({ minimum: 0, maximum: 1 }), + lift: Type.Number() +}); +var LiftV2SpecSchema = Type.Intersect([ + BaseChartV2SpecSchema, + Type.Object({ + type: Type.Literal("lift"), + data: Type.Array(LiftV2DatumSchema), + x: Type.Literal("ppcr"), + y: Type.Literal("lift") + }) +]); + // src/spec/v2/precision_recall.ts var PrecisionRecallV2DatumSchema = Type.Object({ seriesId: Type.String(), @@ -2857,7 +2874,8 @@ var RtichokeChartSpecV2Schema = Type.Union( RocV2SpecSchema, CalibrationV2SpecSchema, PrecisionRecallV2SpecSchema, - GainsV2SpecSchema + GainsV2SpecSchema, + LiftV2SpecSchema ], { $id: "https://rtichoke.dev/schema/viz/2.0.json", @@ -2865,6 +2883,91 @@ var RtichokeChartSpecV2Schema = Type.Union( } ); +// src/spec/v2/performance-table.ts +var PerformanceMetricIdSchema = Type.Union([ + Type.Literal("true_positives"), + Type.Literal("true_negatives"), + Type.Literal("false_positives"), + Type.Literal("false_negatives"), + Type.Literal("sensitivity"), + Type.Literal("specificity"), + Type.Literal("false_positive_rate"), + Type.Literal("ppv"), + Type.Literal("npv"), + Type.Literal("lift"), + Type.Literal("predicted_positives"), + Type.Literal("ppcr"), + Type.Literal("net_benefit"), + Type.Literal("net_benefit_interventions_avoided") +]); +var PerformanceMetricDefinitionSchema = Type.Object({ + id: PerformanceMetricIdSchema, + label: Type.String() +}); +var PerformanceMetricValueSchema = Type.Object({ + metricId: PerformanceMetricIdSchema, + estimate: Type.Union([Type.Number(), Type.Null()]), + lower: Type.Optional(Type.Union([Type.Number(), Type.Null()])), + upper: Type.Optional(Type.Union([Type.Number(), Type.Null()])) +}); +var OperatingPointSchema = Type.Union([ + Type.Object({ + type: Type.Literal("probability_threshold"), + value: Type.Number() + }), + Type.Object({ + type: Type.Literal("ppcr"), + value: Type.Number({ minimum: 0, maximum: 1 }) + }) +]); +var PerformanceEvaluationContextSchema = Type.Object({ + censoringHeuristic: Type.Optional(Type.String()), + competingEventHeuristic: Type.Optional(Type.String()) +}); +var PerformanceTableRowSchema = Type.Object({ + evaluationId: Type.String(), + horizon: Type.Optional(Type.Number({ minimum: 0 })), + operatingPoint: OperatingPointSchema, + context: Type.Optional(PerformanceEvaluationContextSchema), + values: Type.Array(PerformanceMetricValueSchema) +}); +var PerformanceTableSpecSchema = Type.Object({ + schemaVersion: Type.Literal("2.0"), + type: Type.Literal("performance_table"), + title: Type.Optional(Type.String()), + evaluations: Type.Array(EvaluationSpecSchema), + metrics: Type.Array(PerformanceMetricDefinitionSchema), + rows: Type.Array(PerformanceTableRowSchema) +}); + +// src/spec/v2/validate-performance-table.ts +function assertPerformanceTableReferentialIntegrity(spec) { + const evaluationIds = /* @__PURE__ */ new Set(); + for (const evaluation of spec.evaluations) { + if (evaluationIds.has(evaluation.id)) { + throw new Error(`duplicate evaluation id: ${evaluation.id}`); + } + evaluationIds.add(evaluation.id); + } + const metricIds = /* @__PURE__ */ new Set(); + for (const metric of spec.metrics) { + if (metricIds.has(metric.id)) { + throw new Error(`duplicate metric id: ${metric.id}`); + } + metricIds.add(metric.id); + } + for (const row of spec.rows) { + if (!evaluationIds.has(row.evaluationId)) { + throw new Error(`unknown evaluation id: ${row.evaluationId}`); + } + for (const value of row.values) { + if (!metricIds.has(value.metricId)) { + throw new Error(`unknown metric id: ${value.metricId}`); + } + } + } +} + // src/spec/v2/validate.ts function assertV2ReferentialIntegrity(spec) { const evaluationIds = new Set(spec.evaluations.map((evaluation) => evaluation.id)); @@ -18843,7 +18946,7 @@ function renderRocV2(spec, options = {}) { ...datum2, false_positive_rate: 1 - datum2.specificity, title: tooltip(theme.tip.digits, [ - ["Model", datum2.label], + ["Series", datum2.label], ["Cutoff", datum2.cutoff], ["Sensitivity", datum2.sensitivity], ["Specificity", datum2.specificity] @@ -18879,7 +18982,7 @@ function renderCalibrationV2(spec, options = {}) { const data = seriesRenderData(spec, spec.data).map((datum2) => ({ ...datum2, title: tooltip(theme.tip.digits, [ - ["Model", datum2.label], + ["Series", datum2.label], ["Predicted", datum2.predicted], ["Observed", datum2.observed], ["Events", datum2.events], @@ -18935,7 +19038,7 @@ function renderCalibrationV2(spec, options = {}) { (datum2) => ({ ...datum2, title: tooltip(theme.tip.digits, [ - ["Model", datum2.label], + ["Series", datum2.label], ["Midpoint", datum2.midpoint], ["Count", datum2.count] ]) @@ -18989,13 +19092,14 @@ function renderLineChart(spec, options, x2, y2) { spec.data ).map((datum2) => { const values2 = datum2; + const yLabel = y2 === "ppv" ? "PPV" : y2 === "sensitivity" ? "Sensitivity" : "Lift"; return { ...datum2, title: tooltip(theme.tip.digits, [ - ["Model", datum2.label], + ["Series", datum2.label], ["Cutoff", values2.cutoff], [x2 === "ppcr" ? "PPCR" : "Sensitivity", values2[x2]], - [y2 === "ppv" ? "PPV" : "Sensitivity", values2[y2]] + [yLabel, values2[y2]] ]) }; }); @@ -19076,6 +19180,9 @@ function renderPrecisionRecallV2(spec, options = {}) { function renderGainsV2(spec, options = {}) { return renderHorizonLineChart(spec, options, "ppcr", "sensitivity"); } +function renderLiftV2(spec, options = {}) { + return renderHorizonLineChart(spec, options, "ppcr", "lift"); +} export { CalibrationSpecSchema, CalibrationV2SpecSchema, @@ -19083,6 +19190,14 @@ export { DisplayRoleSchema, EvaluationSpecSchema, GainsV2SpecSchema, + LiftV2SpecSchema, + OperatingPointSchema, + PerformanceEvaluationContextSchema, + PerformanceMetricDefinitionSchema, + PerformanceMetricIdSchema, + PerformanceMetricValueSchema, + PerformanceTableRowSchema, + PerformanceTableSpecSchema, PrecisionRecallV2SpecSchema, RTICHOKE_BROWSER_THEME, RTICHOKE_COLORS3 as RTICHOKE_COLORS, @@ -19092,12 +19207,14 @@ export { RtichokeChartSpecSchema, RtichokeChartSpecV2Schema, SeriesSpecSchema, + assertPerformanceTableReferentialIntegrity, assertV2ReferentialIntegrity, calibrationSpecFromRtichokeRows, calibrationV2SpecFromRtichokeRows, renderCalibration, renderCalibrationV2, renderGainsV2, + renderLiftV2, renderPrecisionRecallV2, renderRoc, renderRocV2, @@ -19107,4 +19224,3 @@ export { rocV2SpecFromRtichokePython, rocV2SpecFromRtichokeR }; - diff --git a/tests/test_lift_v2.py b/tests/test_lift_v2.py index 7e242bed..64b3bfc7 100644 --- a/tests/test_lift_v2.py +++ b/tests/test_lift_v2.py @@ -1,9 +1,9 @@ import matplotlib.figure import numpy as np import plotly.graph_objects as go -import pytest from rtichoke import create_lift_curve +from rtichoke._renderers import RtichokeBrowserChart from rtichoke._viz_spec_v2 import _lift_v2_spec_from_performance_data from rtichoke.performance_data.performance_data import prepare_performance_data from rtichoke.processing.evaluation_semantics import ( @@ -158,7 +158,7 @@ def test_equal_prevalence_distinct_populations_maintain_separate_references(): assert perfect_refs[0]["points"] == perfect_refs[1]["points"] -def test_static_lift_renderers(): +def test_static_lift_renderers(tmp_path): probs, reals = _shared_model_inputs() default_fig = create_lift_curve(probs, reals, by=0.25) @@ -167,8 +167,7 @@ def test_static_lift_renderers(): mpl_fig = create_lift_curve(probs, reals, by=0.25, renderer="matplotlib") assert isinstance(mpl_fig, matplotlib.figure.Figure) - with pytest.raises( - ValueError, - match="Browser rendering for Lift curves requires a newer vendored release of rtichoke_viz", - ): - create_lift_curve(probs, reals, by=0.25, renderer="browser") + browser = create_lift_curve(probs, reals, by=0.25, renderer="browser") + assert isinstance(browser, RtichokeBrowserChart) + html = browser.write_html(tmp_path / "lift.html").read_text(encoding="utf-8") + assert "renderLiftV2" in html diff --git a/tests/test_rtichoke_viz_vendor.py b/tests/test_rtichoke_viz_vendor.py index 2e6618de..78883c39 100644 --- a/tests/test_rtichoke_viz_vendor.py +++ b/tests/test_rtichoke_viz_vendor.py @@ -4,31 +4,41 @@ _VENDOR = Path(__file__).parents[1] / "src" / "rtichoke" / "_vendor" / "rtichoke_viz" +_RELEASE_DIR = "rtichoke-viz-0.4.0" -def test_vendored_rtichoke_viz_v031_provenance_archive_and_schemas(): +def test_vendored_rtichoke_viz_v040_provenance_archive_and_schemas(): provenance = (_VENDOR / "VENDORED_FROM").read_text() - assert "release=v0.3.1" in provenance - assert "source_commit=5ccde928a0bf9fa6ece2b7572687b442c57a98a9" in provenance - assert "archive=rtichoke-viz-0.3.1.tar.gz" in provenance + assert "release=v0.4.0" in provenance + assert "source_commit=2125c7099839cadd536c6f38f3f7e23a17ca4348" in provenance + assert "archive=rtichoke-viz-0.4.0.tar.gz" in provenance assert ( - "sha256=121aa8eb8d0f8427ecfb2c01dab0fb05668eaedf47ddcfc0cd282a7ecf1ce448" + "sha256=9a687cd938f1875d577e592664ca75447455166169f6132dd7f79406515e14e1" in provenance ) - archive = _VENDOR / "rtichoke-viz-0.3.1.tar.gz" + archive = _VENDOR / "rtichoke-viz-0.4.0.tar.gz" assert hashlib.sha256(archive.read_bytes()).hexdigest() == ( - "121aa8eb8d0f8427ecfb2c01dab0fb05668eaedf47ddcfc0cd282a7ecf1ce448" + "9a687cd938f1875d577e592664ca75447455166169f6132dd7f79406515e14e1" ) with tarfile.open(archive, "r:gz") as release: assert set(release.getnames()) == { - "rtichoke-viz-0.3.1", - "rtichoke-viz-0.3.1/MANIFEST", - "rtichoke-viz-0.3.1/rtichoke-viz.css", - "rtichoke-viz-0.3.1/rtichoke-viz.js", - "rtichoke-viz-0.3.1/rtichoke-viz.schema.json", - "rtichoke-viz-0.3.1/rtichoke-viz-v2.schema.json", + _RELEASE_DIR, + f"{_RELEASE_DIR}/MANIFEST", + f"{_RELEASE_DIR}/rtichoke-viz.css", + f"{_RELEASE_DIR}/rtichoke-viz.js", + f"{_RELEASE_DIR}/rtichoke-viz.schema.json", + f"{_RELEASE_DIR}/rtichoke-viz-v2.schema.json", } + for filename in ( + "rtichoke-viz.css", + "rtichoke-viz.js", + "rtichoke-viz.schema.json", + "rtichoke-viz-v2.schema.json", + ): + packaged = release.extractfile(f"{_RELEASE_DIR}/{filename}") + assert packaged is not None + assert (_VENDOR / filename).read_bytes() == packaged.read() assert (_VENDOR / "rtichoke-viz.js").stat().st_size > 0 assert (_VENDOR / "rtichoke-viz.css").stat().st_size > 0 @@ -39,13 +49,14 @@ def test_vendored_rtichoke_viz_v031_provenance_archive_and_schemas(): assert '"$id": "https://rtichoke.dev/schema/viz/2.0.json"' in v2_schema -def test_v031_bundle_keeps_v1_adds_v2_exports_and_time_horizon_control(): +def test_v040_bundle_keeps_existing_exports_adds_lift_and_time_horizon_control(): bundle = (_VENDOR / "rtichoke-viz.js").read_text(encoding="utf-8") for export_name in ( "renderRoc", "renderCalibration", "RtichokeChartSpecSchema", "renderGainsV2", + "renderLiftV2", "RtichokeChartSpecV2Schema", ): assert export_name in bundle diff --git a/tests/test_time_lift_v2.py b/tests/test_time_lift_v2.py index c7335390..257ee6ee 100644 --- a/tests/test_time_lift_v2.py +++ b/tests/test_time_lift_v2.py @@ -1,9 +1,9 @@ import matplotlib.figure import numpy as np import plotly.graph_objects as go -import pytest from rtichoke import create_lift_curve_times +from rtichoke._renderers import RtichokeBrowserChart from rtichoke._viz_spec_v2 import _lift_times_v2_spec_from_performance_data from rtichoke.performance_data.performance_data_times import ( prepare_performance_data_times, @@ -161,26 +161,25 @@ def test_time_lift_censoring_and_competing_risk_reference_comes_from_performance ] -def test_time_lift_renderers_preserve_plotly_default_and_horizons(): +def test_time_lift_renderers_preserve_plotly_default_and_horizons(tmp_path): probs, reals, times = _shared_inputs() default = create_lift_curve_times( probs, reals, times, HORIZONS, heuristics_sets=HEURISTICS, by=0.25 ) assert isinstance(default, go.Figure) - with pytest.raises( - ValueError, - match="Browser rendering for Lift curves requires a newer vendored release of rtichoke_viz", - ): - create_lift_curve_times( - probs, - reals, - times, - HORIZONS, - heuristics_sets=HEURISTICS, - by=0.25, - renderer="browser", - ) + browser = create_lift_curve_times( + probs, + reals, + times, + HORIZONS, + heuristics_sets=HEURISTICS, + by=0.25, + renderer="browser", + ) + assert isinstance(browser, RtichokeBrowserChart) + html = browser.write_html(tmp_path / "lift-times.html").read_text(encoding="utf-8") + assert "renderLiftV2" in html matplotlib_result = create_lift_curve_times( probs,