# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca from __future__ import annotations import json from datetime import date import pytest from agentic_valuation.agents.packets import build_packet, committee_state, render_user_prompt, role_view, verify_evidence, visible_ids from agentic_valuation.agents.roles import get_role from agentic_valuation.schemas import Adjustment, AgentEstimate, Comparable from tests.conftest_agents import make_comps, make_geo, make_market, make_row, packet, pool # noqa: F401 def test_packet_never_contains_price_or_street(packet): js = packet.model_dump_json() assert "487123" not in js and "street" not in js.lower() and "secr" not in js.lower() assert "price" not in packet.subject.model_dump() or packet.subject.model_dump().get("price") is None assert packet.subject.lat == 45.512 and packet.subject.lng == -73.612 assert packet.subject.information_cutoff == date(2025, 5, 9) assert packet.evidence_ids() == {"P", "M1", "G1", *[f"C{i:02d}" for i in range(1, 7)]} def test_missing_fields_and_leakage_guards(): row = make_row(floor_area_m2=None, year_built=None, prior_sale_price=300000.0, prior_sale_date=date(2025, 5, 9)) 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)] pk = build_packet(row, comps, make_market(), None, {"comparables": {"max_comps": 12}}) assert "floor_area_m2" in pk.subject.missing_fields and "year_built" in pk.subject.missing_fields assert all(c.sale_date <= pk.subject.information_cutoff for c in pk.comparables) assert [c.evidence_id for c in pk.comparables] == [f"C{i:02d}" for i in range(1, 7)] assert pk.subject.prior_sale_price is None # prior sale on/after the cutoff is dropped assert pk.geo is None and any("No geospatial" in n for n in pk.notes) def test_role_view_filtering(packet): v = role_view(packet, get_role("building_condition")) assert "subject" in v and "comparables" not in v and "market" not in v and "geo" not in v assert v["evidence_ids_visible"] == ["P"] v2 = role_view(packet, get_role("market_economist")) assert set(v2["evidence_ids_visible"]) == {"P", "M1"} v3 = role_view(packet, get_role("skeptic")) assert set(v3["evidence_ids_visible"]) == packet.evidence_ids() assert [c["evidence_id"] for c in v3["comparables"]] == [c.evidence_id for c in packet.comparables] assert visible_ids(packet, get_role("comparables")) == {"P", *[f"C{i:02d}" for i in range(1, 7)]} def test_render_user_prompt_blocks(packet): v = role_view(packet, get_role("hedonic")) p = render_user_prompt(v, None, kind="estimate") assert "" in p and "" in p and "AgentEstimate" in p est = AgentEstimate(point_estimate=500000, q05=420000, q25=470000, q50=500000, q75=530000, q95=590000, evidence_ids_used=["P", "C01"], reasoning_summary="x") state = committee_state(1, {"hedonic": est, "comparables": est}, for_role="hedonic", critiques=[{"from": "committee_member", "agree": False, "points": ["check C01"]}], visible={"P"}) p2 = render_user_prompt(v, state, kind="revision", verification_flags=["unknown_evidence_id:M1"]) assert '"committee_median":500000' in p2.replace(" ", "") assert "" in p2 and "" in p2 inner = p2.split("")[1].split("")[0] js = json.loads(inner) assert js["estimates"][0]["role"] == "hedonic" and js["evidence_ids_cited_by_others_outside_your_view"] == ["C01"] assert js["critiques_addressed_to_you"][0]["points"] == ["check C01"] with pytest.raises(ValueError): render_user_prompt(v, None, kind="nope") def test_verify_evidence_flags(packet): 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")]) assert verify_evidence(good, packet) == [] bad = AgentEstimate( point_estimate=900000, q05=800000, q25=850000, q50=900000, q75=950000, q95=990000, evidence_ids_used=["P", "C77", "Z1"], adjustments=[Adjustment(comparable_id="C01", adjusted_price=800000, reason="x"), Adjustment(comparable_id="C50", adjusted_price=1, reason="x")], ) flags = verify_evidence(bad, packet) assert "unknown_evidence_id:C77" in flags and "unknown_evidence_id:Z1" in flags assert "adjustment_out_of_band:C01" in flags and "unknown_comparable:C50" in flags assert "point_outside_comparable_hull" in flags # citing an id outside the role's view is flagged when allowed_ids is given assert verify_evidence(good, packet, allowed_ids={"P"}) == ["unknown_evidence_id:C01"]