portfolio-optimiser/tests/test_verdicts.py

108 lines
4.1 KiB
Python

"""Step 3 tests — non-tautological top-K retrieval + REAL-SessionContext two-arg injection.
The true match shares the *structured* similarity fields with the query but uses different
description text; the decoys share surface text but differ structurally. The injection test
uses a REAL ``agent_framework.SessionContext`` (not a single-arg fake), exercising the
genuine two-arg ``extend_instructions(source_id, instructions)`` GA signature — retiring the
Critical Fase 1 risk. Pattern: tests/spikes/test_d_verdictstore.py + real SessionContext.
"""
import pytest
from agent_framework import SessionContext
from portfolio_optimiser.verdicts import (
ExpeLContextProvider,
ProposalFeatures,
Verdict,
VerdictStore,
capture_verdict,
)
_QUERY = ProposalFeatures(
affected_codes=frozenset({"05.2", "03.1"}),
measure_type="scope_reduction",
claimed_saving_nok=220_000, # bucket [100k, 500k)
description="asphalt base course reduction near school",
)
def _store_with_true_match_and_decoys() -> tuple[VerdictStore, str]:
true_match = Verdict(
id="TRUE",
proposal_features=ProposalFeatures(
affected_codes=frozenset({"05.2", "03.1"}), # same codes
measure_type="scope_reduction", # same measure type
claimed_saving_nok=200_000, # same magnitude bucket
description="zzz totally unrelated wording alpha beta", # DIFFERENT text
),
decision="approved",
rationale="prior scope reduction on the same codes was approved",
)
decoy_low = Verdict(
id="DECOY-LOW",
proposal_features=ProposalFeatures(
affected_codes=frozenset({"09.1"}),
measure_type="rate_renegotiation",
claimed_saving_nok=50_000,
description="asphalt base course reduction near school", # same words as query
),
decision="rejected",
rationale="surface-text decoy",
)
decoy_high = Verdict(
id="DECOY-HIGH",
proposal_features=ProposalFeatures(
affected_codes=frozenset({"21.2"}),
measure_type="material_substitution",
claimed_saving_nok=700_000,
description="asphalt base course reduction extra words",
),
decision="rejected",
rationale="surface-text decoy",
)
return VerdictStore(verdicts=[decoy_low, true_match, decoy_high]), "TRUE"
def test_retrieve_finds_structural_match_over_text_decoys() -> None:
store, true_id = _store_with_true_match_and_decoys()
hits = store.retrieve(_QUERY, k=3)
assert hits[0].id == true_id # structural match ranks #1 despite different wording
def test_retrieve_is_deterministic() -> None:
store, _ = _store_with_true_match_and_decoys()
assert [h.id for h in store.retrieve(_QUERY, k=3)] == [
h.id for h in store.retrieve(_QUERY, k=3)
]
def test_retrieve_rejects_non_positive_k() -> None:
store, _ = _store_with_true_match_and_decoys()
with pytest.raises(ValueError):
store.retrieve(_QUERY, k=0)
def test_capture_verdict_mints_stable_id() -> None:
a = capture_verdict(_QUERY, "approved", "ok")
b = capture_verdict(
ProposalFeatures(
affected_codes=frozenset({"03.1", "05.2"}), # same set, different order
measure_type="scope_reduction",
claimed_saving_nok=220_000,
description="DIFFERENT surface wording entirely", # text excluded from the id
),
"approved",
"ok",
)
assert a.id == b.id # structurally identical -> stable id
assert len(a.id) == 16
async def test_before_run_populates_real_sessioncontext_two_arg() -> None:
store, true_id = _store_with_true_match_and_decoys()
provider = ExpeLContextProvider(store, _QUERY, k=2)
# A REAL SessionContext (not a single-arg fake) — exercises the genuine GA two-arg
# extend_instructions(source_id, instructions) signature.
ctx = SessionContext(input_messages=[], instructions=[])
await provider.before_run(agent=None, session=None, context=ctx, state={})
assert any(true_id in instr for instr in ctx.instructions)