feat(fase2a): percent-mål mot baseline 0 reiser ValueError (S2.0)

This commit is contained in:
Kjell Tore Guttormsen 2026-07-15 07:14:29 +02:00
commit 9311813080
2 changed files with 23 additions and 1 deletions

View file

@ -420,6 +420,12 @@ def _goal_limit_if_reached(goal: GoalContract, observed_ore: int, baseline_ore:
if goal.absolute_ore is not None and observed_ore >= goal.absolute_ore:
return goal.absolute_ore
if goal.percent is not None:
if baseline_ore <= 0:
raise ValueError(
f"percent goal ({goal.percent}%) is meaningless against a non-positive baseline "
f"({baseline_ore} øre): int(percent/100 * 0) == 0 would falsely read as 'goal "
"reached'. Supply an absolute_ore target, or ensure the addressable baseline is > 0."
)
threshold = int(goal.percent / 100 * baseline_ore)
if observed_ore >= threshold:
return threshold

View file

@ -18,8 +18,11 @@ from __future__ import annotations
from importlib.resources import files
import pytest
from portfolio_optimiser.contracts import GoalContract
from portfolio_optimiser.reference_domain import Project
from portfolio_optimiser.run import run_portfolio
from portfolio_optimiser.run import _goal_limit_if_reached, run_portfolio
from portfolio_optimiser.verdicts import ProposalFeatures, capture_verdict, write_verdict
_MINI_BUNDLE = str(files("portfolio_optimiser").joinpath("data/bundles/bygg-energi-mikro-a"))
@ -166,3 +169,16 @@ async def test_dropped_verdict_in_kplus1_inbox_reaches_store(
"a verdict dropped as a file in k+1's verdict_dir inbox did not reach the store — the "
"verdict_dir threading in run_portfolio is detached"
)
def test_percent_goal_against_zero_baseline_raises_value_error() -> None:
"""T-2.0c (direct unit test of the chokepoint): a percent goal against a non-positive baseline is
meaningless ``int(percent/100 * 0) == 0`` would falsely read as 'goal reached'. The guard in
``_goal_limit_if_reached`` raises ``ValueError`` instead. Detach the guard no raise (false
'reached') RED. Tested DIRECTLY on the function: ``_goal_limit_if_reached`` is only called
inside a non-empty loop with ``baseline=sum(total_cost)``, and no reference project has zero
cost, so a baseline-0 path is not constructable via ``run_portfolio`` the direct call is the
unambiguous, constructable test."""
goal = GoalContract(percent=10.0)
with pytest.raises(ValueError):
_goal_limit_if_reached(goal, observed_ore=0, baseline_ore=0)