diff --git a/frontend/src/leftoverMapPlotAxisSingular.ts b/frontend/src/leftoverMapPlotAxisSingular.ts index 7a2111c08..7f7b81b02 100644 --- a/frontend/src/leftoverMapPlotAxisSingular.ts +++ b/frontend/src/leftoverMapPlotAxisSingular.ts @@ -48,6 +48,12 @@ export const LEFTOVER_MAP_COMPARE_PLOT_TICK_SINGULAR_SHARE = export const LEFTOVER_MAP_COMPARE_AXIS_TICK_SINGULAR = "leftover map comparison leftover axis {axis} tick {value} σ {singular}"; +export const LEFTOVER_MAP_COMPARE_AXIS_TICK_SHARE = + "leftover map comparison leftover axis {axis} tick {value} {share}%"; + +export const LEFTOVER_MAP_COMPARE_AXIS_TICK_SINGULAR_SHARE = + "leftover map comparison leftover axis {axis} tick {value} σ {singular} {share}%"; + export type LeftoverMapPlotAxisSingular = { axis_index: LeftoverMapAxis["axis_index"]; leftover_singular_value?: LeftoverMapAxis["leftover_singular_value"] | null; @@ -213,18 +219,32 @@ export function leftoverMapComparePlotTickAxisBadge( }; } -/** Project finite persisted σ onto one comparison-strip tick without inventing share evidence. */ +/** Compose persisted comparison-strip tick σ/share without deriving either field. */ export function leftoverMapCompareAxisTickBadge( axisIndex: number, tickLabel: string, leftoverSingular: number | null | undefined, + leftoverShare?: LeftoverMapAxis["leftover_share"] | null, ): LeftoverMapCompareAxisBadge | null { const singular = formatLeftoverMapPlotAxisSingular(leftoverSingular); - if (singular === null) { + const share = formatLeftoverMapPlotAxisShare(leftoverShare); + if (singular === null && share === null) { return null; } + if (singular === null) { + return { + template: LEFTOVER_MAP_COMPARE_AXIS_TICK_SHARE, + values: { axis: axisIndex, value: tickLabel, share: share as string }, + }; + } + if (share === null) { + return { + template: LEFTOVER_MAP_COMPARE_AXIS_TICK_SINGULAR, + values: { axis: axisIndex, value: tickLabel, singular }, + }; + } return { - template: LEFTOVER_MAP_COMPARE_AXIS_TICK_SINGULAR, - values: { axis: axisIndex, value: tickLabel, singular }, + template: LEFTOVER_MAP_COMPARE_AXIS_TICK_SINGULAR_SHARE, + values: { axis: axisIndex, value: tickLabel, singular, share }, }; } diff --git a/tests/test_grouping_comparison_strip_tick_share_contract.py b/tests/test_grouping_comparison_strip_tick_share_contract.py new file mode 100644 index 000000000..6aa38226c --- /dev/null +++ b/tests/test_grouping_comparison_strip_tick_share_contract.py @@ -0,0 +1,29 @@ +"""Executable contract for comparison-strip axis tick share captions.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +SINGULAR_SOURCE = ROOT / "frontend" / "src" / "leftoverMapPlotAxisSingular.ts" + + +def test_comparison_strip_tick_keeps_axis_share_independent_of_singular() -> None: + """A comparison-strip tick may name persisted share when singular evidence is absent.""" + assert SINGULAR_SOURCE.exists(), "comparison-strip axis projection helper is missing" + source = SINGULAR_SOURCE.read_text(encoding="utf-8") + + assert "leftoverMapCompareAxisTickBadge" in source + assert "leftover map comparison leftover axis {axis} tick {value} {share}%" in source + assert "leftover map comparison leftover axis {axis} tick {value} σ {singular} {share}%" in source + + +def test_comparison_strip_tick_never_infers_share_from_singular() -> None: + """Share-only, singular-only, combined, and empty states remain persisted-data decisions.""" + assert SINGULAR_SOURCE.exists(), "comparison-strip axis projection helper is missing" + source = SINGULAR_SOURCE.read_text(encoding="utf-8") + + assert "Number.isFinite" in source + assert "leftover_share" in source + assert "formatLeftoverMapPlotAxisSingular" in source + assert "Math.sqrt" not in source + assert "Math.max" not in source diff --git a/tests/test_grouping_comparison_strip_tick_singular_contract.py b/tests/test_grouping_comparison_strip_tick_singular_contract.py index 75a923062..1bb5e2a69 100644 --- a/tests/test_grouping_comparison_strip_tick_singular_contract.py +++ b/tests/test_grouping_comparison_strip_tick_singular_contract.py @@ -17,12 +17,17 @@ def test_comparison_strip_axis_tick_keeps_persisted_singular_value_without_share def test_comparison_strip_axis_tick_does_not_infer_sigma_or_share() -> None: - """Invalid σ falls back to the comparison-axis tick and no counterpart is synthesized.""" + """Comparison-strip ticks compose persisted σ/share without deriving either field.""" 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 - tail = source.split("leftoverMapCompareAxisTickBadge", 1)[-1][:1600] - assert "leftover_share" not in tail + helper = source.split("export function leftoverMapCompareAxisTickBadge", 1)[-1] + + assert "formatLeftoverMapPlotAxisSingular" in helper + assert "formatLeftoverMapPlotAxisShare" in helper + assert "leftoverSingular" in helper + assert "leftoverShare" in helper + assert "LEFTOVER_MAP_COMPARE_AXIS_TICK_SHARE" in helper + assert "LEFTOVER_MAP_COMPARE_AXIS_TICK_SINGULAR_SHARE" in helper + assert "Math.sqrt" not in helper + assert "Math.max" not in helper + assert "Math.min" not in helper