1# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca2from __future__ import annotations34import json5from datetime import date67import pytest89from agentic_valuation.agents.packets import build_packet, committee_state, render_user_prompt, role_view, verify_evidence, visible_ids10from agentic_valuation.agents.roles import get_role11from agentic_valuation.schemas import Adjustment, AgentEstimate, Comparable12from tests.conftest_agents import make_comps, make_geo, make_market, make_row, packet, pool # noqa: F401131415def test_packet_never_contains_price_or_street(packet):16 js = packet.model_dump_json()17 assert "487123" not in js and "street" not in js.lower() and "secr" not in js.lower()18 assert "price" not in packet.subject.model_dump() or packet.subject.model_dump().get("price") is None19 assert packet.subject.lat == 45.512 and packet.subject.lng == -73.61220 assert packet.subject.information_cutoff == date(2025, 5, 9)21 assert packet.evidence_ids() == {"P", "M1", "G1", *[f"C{i:02d}" for i in range(1, 7)]}222324def test_missing_fields_and_leakage_guards():25 row = make_row(floor_area_m2=None, year_built=None, prior_sale_price=300000.0, prior_sale_date=date(2025, 5, 9))26 comps = make_comps() + [Comparable(evidence_id="C99", sale_date=date(2025, 5, 10), price=1.0, distance_km=0.1, property_class="house", municipality="X", months_before_valuation=0.0)]27 pk = build_packet(row, comps, make_market(), None, {"comparables": {"max_comps": 12}})28 assert "floor_area_m2" in pk.subject.missing_fields and "year_built" in pk.subject.missing_fields29 assert all(c.sale_date <= pk.subject.information_cutoff for c in pk.comparables)30 assert [c.evidence_id for c in pk.comparables] == [f"C{i:02d}" for i in range(1, 7)]31 assert pk.subject.prior_sale_price is None # prior sale on/after the cutoff is dropped32 assert pk.geo is None and any("No geospatial" in n for n in pk.notes)333435def test_role_view_filtering(packet):36 v = role_view(packet, get_role("building_condition"))37 assert "subject" in v and "comparables" not in v and "market" not in v and "geo" not in v38 assert v["evidence_ids_visible"] == ["P"]39 v2 = role_view(packet, get_role("market_economist"))40 assert set(v2["evidence_ids_visible"]) == {"P", "M1"}41 v3 = role_view(packet, get_role("skeptic"))42 assert set(v3["evidence_ids_visible"]) == packet.evidence_ids()43 assert [c["evidence_id"] for c in v3["comparables"]] == [c.evidence_id for c in packet.comparables]44 assert visible_ids(packet, get_role("comparables")) == {"P", *[f"C{i:02d}" for i in range(1, 7)]}454647def test_render_user_prompt_blocks(packet):48 v = role_view(packet, get_role("hedonic"))49 p = render_user_prompt(v, None, kind="estimate")50 assert "<evidence_packet>" in p and "</evidence_packet>" in p and "AgentEstimate" in p51 est = AgentEstimate(point_estimate=500000, q05=420000, q25=470000, q50=500000, q75=530000, q95=590000, evidence_ids_used=["P", "C01"], reasoning_summary="x")52 state = committee_state(1, {"hedonic": est, "comparables": est}, for_role="hedonic", critiques=[{"from": "committee_member", "agree": False, "points": ["check C01"]}], visible={"P"})53 p2 = render_user_prompt(v, state, kind="revision", verification_flags=["unknown_evidence_id:M1"])54 assert '"committee_median":500000' in p2.replace(" ", "")55 assert "<committee_state>" in p2 and "<verification_flags>" in p256 inner = p2.split("<committee_state>")[1].split("</committee_state>")[0]57 js = json.loads(inner)58 assert js["estimates"][0]["role"] == "hedonic" and js["evidence_ids_cited_by_others_outside_your_view"] == ["C01"]59 assert js["critiques_addressed_to_you"][0]["points"] == ["check C01"]60 with pytest.raises(ValueError):61 render_user_prompt(v, None, kind="nope")626364def test_verify_evidence_flags(packet):65 good = AgentEstimate(point_estimate=470000, q05=400000, q25=450000, q50=470000, q75=490000, q95=540000, evidence_ids_used=["P", "C01"], adjustments=[Adjustment(comparable_id="C01", adjusted_price=460000, reason="time")])66 assert verify_evidence(good, packet) == []67 bad = AgentEstimate(68 point_estimate=900000, q05=800000, q25=850000, q50=900000, q75=950000, q95=990000, evidence_ids_used=["P", "C77", "Z1"],69 adjustments=[Adjustment(comparable_id="C01", adjusted_price=800000, reason="x"), Adjustment(comparable_id="C50", adjusted_price=1, reason="x")],70 )71 flags = verify_evidence(bad, packet)72 assert "unknown_evidence_id:C77" in flags and "unknown_evidence_id:Z1" in flags73 assert "adjustment_out_of_band:C01" in flags and "unknown_comparable:C50" in flags74 assert "point_outside_comparable_hull" in flags75 # citing an id outside the role's view is flagged when allowed_ids is given76 assert verify_evidence(good, packet, allowed_ids={"P"}) == ["unknown_evidence_id:C01"]77