diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx index 4fc42abd6..9f63b5d5e 100644 --- a/frontend/src/components/LeftoverMapPlot.tsx +++ b/frontend/src/components/LeftoverMapPlot.tsx @@ -30,6 +30,7 @@ import { import { formatLeftoverMapPlotAxisSingular, leftoverMapComparePlotAxisBadge, + leftoverMapPlotTickAxisBadge, leftoverSingularForAxis, LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR, LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE, @@ -360,30 +361,42 @@ export function LeftoverMapPlot({ > {leftoverMapPlotAxisText(2, leftoverMapAxes, variant)} - {layout.ticks.map((tick) => ( - - - { + const reportTickBadge = + variant === "report" + ? leftoverMapPlotTickAxisBadge( + tick.axis, + tick.label, + leftoverSingularForAxis(leftoverMapAxes, tick.axis), + ) + : null; + const tickAriaLabel = + variant === "comparison" + ? `${t(LEFTOVER_MAP_COMPARE_PLOT_LABEL)}: ${tf(LEFTOVER_MAP_PLOT_TICK, { + axis: tick.axis, + value: tick.label, + })}` + : reportTickBadge !== null + ? tf(reportTickBadge.template, reportTickBadge.values) + : tf(LEFTOVER_MAP_PLOT_TICK, { axis: tick.axis, value: tick.label }); + return ( + - {tick.label} - - - ))} + + + {tick.label} + + + ); + })} {layout.segments.map((segment) => ( None: - """Comparison ticks compose existing localized comparison and tick copy.""" + """Comparison ticks keep localized comparison copy while report ticks may add persisted σ evidence.""" plot_source = PLOT_SOURCE.read_text(encoding="utf-8") - assert "LEFTOVER_MAP_COMPARE_PLOT_LABEL" in plot_source - assert "LEFTOVER_MAP_PLOT_TICK" in plot_source + # v2.84 adds a report-only evidence badge. Comparison ticks must keep the + # existing localized comparison + generic tick composition rather than + # inheriting report evidence or introducing a comparison-only translation. + assert "leftoverMapPlotTickAxisBadge" in plot_source + assert "leftoverSingularForAxis(leftoverMapAxes, tick.axis)" in plot_source assert re.search( - r'variant\s*===\s*"comparison"\s*\?\s*`\$\{t\(LEFTOVER_MAP_COMPARE_PLOT_LABEL\)\}:\s*\$\{tf\(LEFTOVER_MAP_PLOT_TICK,\s*\{\s*axis:\s*tick\.axis,\s*value:\s*tick\.label,?\s*\}\)\}`\s*:\s*tf\(LEFTOVER_MAP_PLOT_TICK,\s*\{\s*axis:\s*tick\.axis,\s*value:\s*tick\.label,?\s*\}\)', + r'reportTickBadge\s*=\s*variant\s*===\s*"report"\s*\?\s*leftoverMapPlotTickAxisBadge\(', plot_source, re.DOTALL, ) + assert re.search( + r'variant\s*===\s*"comparison"\s*\?\s*`\$\{t\(LEFTOVER_MAP_COMPARE_PLOT_LABEL\)\}:\s*\$\{tf\(LEFTOVER_MAP_PLOT_TICK,\s*\{\s*axis:\s*tick\.axis,\s*value:\s*tick\.label,?\s*\}\)\}`', + plot_source, + re.DOTALL, + ) + assert "tf(reportTickBadge.template, reportTickBadge.values)" in plot_source assert "LEFTOVER_MAP_COMPARE_PLOT_TICK" not in plot_source diff --git a/tests/test_leftover_map_report_tick_singular_contract.py b/tests/test_leftover_map_report_tick_singular_contract.py new file mode 100644 index 000000000..cfd1da29b --- /dev/null +++ b/tests/test_leftover_map_report_tick_singular_contract.py @@ -0,0 +1,37 @@ +"""Executable contract for report-graphic tick singular-value captions.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +SINGULAR_SOURCE = ROOT / "frontend" / "src" / "leftoverMapPlotAxisSingular.ts" +PLOT_COMPONENT_SOURCE = ROOT / "frontend" / "src" / "components" / "LeftoverMapPlot.tsx" + + +def test_report_graphic_tick_keeps_persisted_singular_value_without_share() -> None: + """A tick may name valid persisted σ even when axis share is absent.""" + assert SINGULAR_SOURCE.exists(), "singular-value axis helper is missing" + source = SINGULAR_SOURCE.read_text(encoding="utf-8") + + assert "leftoverMapPlotTickAxisBadge" in source + assert "leftover-map axis {axis} tick {value} σ {singular}" in source + + +def test_report_graphic_consumes_tick_singular_projection() -> None: + """The buyer-visible report graphic must consume the persisted-σ tick projection.""" + source = PLOT_COMPONENT_SOURCE.read_text(encoding="utf-8") + + assert "leftoverMapPlotTickAxisBadge" in source + assert "leftoverSingularForAxis(leftoverMapAxes, tick.axis)" in source + assert "tf(reportTickBadge.template, reportTickBadge.values)" in source + + +def test_report_graphic_tick_does_not_infer_sigma_or_share() -> None: + """Invalid σ falls back to the ordinary tick and ticks never synthesize share.""" + assert SINGULAR_SOURCE.exists(), "singular-value axis helper is missing" + source = SINGULAR_SOURCE.read_text(encoding="utf-8") + + assert "formatLeftoverMapPlotAxisSingular" in source + assert "Number.isFinite" in source + assert "Math.sqrt" not in source + assert "leftover_share" not in source.split("leftoverMapPlotTickAxisBadge", 1)[-1][:1400]