1# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca2from __future__ import annotations34import math56import numpy as np7import pytest89from agentic_valuation.debate.aggregation import RULES, aggregate, aggregate_intervals, aggregate_points, fit_weights, load_weights, median_of_quantiles, mixture_quantiles, save_weights10from agentic_valuation.schemas import DebateTranscript1112ROLES = ["a", "b", "c", "d", "e"]131415def _tr(tx, points, q=None):16 return DebateTranscript(tx_id=tx, arm="F", committee="c", protocol="p", rounds=0, packet_hash="h", turns=[], final_estimates=dict(zip(ROLES, points)), final_quantiles={r: (q or [0.8 * p, 0.9 * p, p, 1.1 * p, 1.25 * p]) for r, p in zip(ROLES, points)})171819def test_point_rules_log_space():20 pts = {"a": 400000.0, "b": 500000.0, "c": 450000.0, "d": 480000.0, "e": 2000000.0} # e is an outlier21 out = aggregate_points(pts)22 assert set(RULES) <= set(out)23 assert math.isclose(out["mean"], math.exp(np.mean(np.log(list(pts.values())))))24 assert out["median"] == pytest.approx(480000.0)25 assert out["trimmed_mean_20"] < out["mean"] and out["robust_bayes"] < out["mean"] # robust to the outlier26 assert abs(out["robust_bayes"] - 470000) < 4000027 assert out["inverse_variance"] == pytest.approx(out["mean"]) and out["stacking"] == pytest.approx(out["mean"]) # equal weights when none supplied28 w = {"inverse_variance": {"a": 1.0, "b": 0, "c": 0, "d": 0, "e": 0}}29 assert math.isclose(aggregate_points(pts, w)["inverse_variance"], 400000.0)30 assert aggregate_points({}) == {}313233def test_intervals():34 q = {"a": [380000, 420000, 450000, 480000, 530000], "b": [400000, 440000, 470000, 500000, 560000], "c": [300000, 350000, 400000, 450000, 500000]}35 m = median_of_quantiles(q)36 assert m == pytest.approx([380000, 420000, 450000, 480000, 530000])37 mx = mixture_quantiles(q)38 assert all(b >= a for a, b in zip(mx, mx[1:])) and 300000 <= mx[0] <= 400000 and 500000 <= mx[4] <= 56000039 iv = aggregate_intervals(q)40 assert set(iv) == {"median_of_quantiles", "mixture"}414243def test_fit_weights_and_apply(tmp_path):44 rng = np.random.default_rng(0)45 sds = [0.05, 0.10, 0.20, 0.30, 0.40]46 truth, trs = {}, []47 for i in range(60):48 p = 400000.0 * math.exp(rng.normal(0, 0.3))49 truth[f"t{i}"] = p50 trs.append(_tr(f"t{i}", [p * math.exp(rng.normal(0, s)) for s in sds]))51 w = fit_weights(trs, truth)52 iv = w["inverse_variance"]53 assert math.isclose(sum(iv.values()), 1.0) and iv["a"] > iv["b"] > iv["e"]54 st = w["stacking"]55 assert all(v >= 0 for v in st.values()) and math.isclose(sum(st.values()), 1.0)56 assert 0.0 < w["robust_bayes"]["scale"] < 1.0 and w["robust_bayes"]["df"] == 457 path = save_weights(w, tmp_path / "w.json")58 assert load_weights(path)["inverse_variance"] == pytest.approx(iv)59 # out of sample on fresh draws: inverse-variance beats the plain mean on average60 err_iv, err_mean = [], []61 for i in range(200):62 p = 400000.0 * math.exp(rng.normal(0, 0.3))63 t = _tr(f"v{i}", [p * math.exp(rng.normal(0, s)) for s in sds])64 agg = aggregate(t, w)65 assert set(RULES) <= set(agg)66 err_iv.append(abs(math.log(agg["inverse_variance"]) - math.log(p)))67 err_mean.append(abs(math.log(agg["mean"]) - math.log(p)))68 assert np.mean(err_iv) < np.mean(err_mean)69