"""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. """ from pathlib import Path import pytest from agent_framework import SessionContext from portfolio_optimiser.verdicts import ( ExpeLContextProvider, ProposalFeatures, Verdict, VerdictStore, bundle_candidate_features, capture_verdict, seed_store_from_bundle, ) _BUNDLE_DIR = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro" _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) # --- OKF-bundle seeding (Fase 2a): the pre-hypothesis ExpeL query key + the seed store --- def test_bundle_candidate_features_keys_on_the_ir_projection() -> None: """The pre-hypothesis ExpeL query is the candidate measure's cost-IR features (from the bundle's ``validator-input.json``) — available BEFORE any proposal is generated.""" features = bundle_candidate_features(str(_BUNDLE_DIR)) assert features.affected_codes == frozenset({"ENERGI-TOTAL-EL"}) assert "LED-retrofit" in features.measure_type assert features.claimed_saving_nok == 30000 def test_seed_store_from_bundle_carries_the_realization_signal() -> None: """Each ``type: verdict`` file becomes a structurally-keyed ``Verdict`` whose rationale carries the learning signal the validator cannot compute (the realization rate 0.82).""" store = seed_store_from_bundle(str(_BUNDLE_DIR)) assert len(store.verdicts) == 1 seed = store.verdicts[0] assert seed.proposal_features.affected_codes == frozenset({"ENERGI-TOTAL-EL"}) assert "approved" in seed.decision assert "0.82" in seed.rationale def test_seed_store_retrieval_matches_the_candidate() -> None: """A3: seed and query derive from the SAME IR -> similarity 1.0 -> the lone seed is retrieved for the candidate (the structural match the Step-1 wiring relies on).""" store = seed_store_from_bundle(str(_BUNDLE_DIR)) query = bundle_candidate_features(str(_BUNDLE_DIR)) hits = store.retrieve(query, k=3) assert len(hits) == 1 assert "0.82" in hits[0].rationale