Source: tests/test_v2_reporting.py · Plain text

tests/test_v2_reporting.py

"""Saved-row reporting never retrains or invents confirmation results."""

import json

import pytest

from mcss.vision_probe.v2_reporting import analyze_saved_rows, build_report
from mcss.vision_probe.v2_statistics import TRAJECTORIES


def _raw():
    rows = []
    for sequence, gain_path in (("s1", ("A", "ALL")), ("s2", ("B", "A"))):
        for trajectory in TRAJECTORIES:
            j = 0.8 if trajectory == gain_path else 0.2
            rows.append(
                {
                    "sequence": sequence,
                    "target_slot": 1,
                    "trajectory": list(trajectory),
                    "J": j,
                    "F": j / 2,
                    "JF": 0.75 * j,
                    "objects": [
                        {
                            "object_id": 1,
                            "J": j,
                            "size_bucket": "tiny",
                            "pixel_count": 1,
                            "pixel_fraction": 0.001,
                        }
                    ],
                }
            )
    return rows


def test_report_uses_saved_choices_and_keeps_secondary_tiny_data():
    rows = _raw()
    saved = [{"sequence": s, "target_slot": 1, "trajectory": ["OFF", "OFF"]} for s in ("s1", "s2")]
    result = analyze_saved_rows(rows, {"CycleGate": saved}, ["OFF", "OFF"], draws=50)
    cycle = result["selector_analysis"]["CycleGate"]
    assert cycle["mean_J"] == 0.2  # never replace saved OFF with the answer oracle
    assert cycle["secondary"]["F"] == 0.1
    assert cycle["secondary"]["JF"] == pytest.approx(0.15)
    assert cycle["net_gain"] == 0
    assert result["global_diagnostic"]["gaps"]["sample_dependence_gap"]["mean"] == pytest.approx(
        0.3
    )
    assert result["selector_analysis"]["hindsight_global16"]["comparisons"] == {}
    tiny = result["object_size_sensitivity"]["CycleGate"]["buckets"]["tiny"]
    assert tiny["n_objects"] == 2
    assert tiny["conditional_sequence_macro_gain"] == 0
    assert len(result["per_pair_results"]) == 12
    json.dumps(result, allow_nan=False)


def test_incomplete_saved_decisions_fail_closed():
    with pytest.raises(ValueError, match="every raw pair"):
        analyze_saved_rows(_raw(), {"CycleGate": []}, None, draws=10)


def test_blocked_regeneration_has_no_confirmation_numbers_or_media_reads(tmp_path, monkeypatch):
    import mcss.vision_probe.v2_reporting as reporting

    report, work = tmp_path / "report", tmp_path / "work"
    report.mkdir()
    work.mkdir()
    (report / "config.json").write_text(json.dumps({"schema": "test.v2", "bootstrap_samples": 10}))
    (report / "independent_data_audit.json").write_text(
        json.dumps(
            {
                "status": "BLOCKED_NO_INDEPENDENT_DATA",
                "confirmation_allowed": False,
            }
        )
    )
    old = {"fixed_trajectory_provenance": {"trajectory": ["OFF", "OFF"]}, "diagnostics": {}}
    monkeypatch.setattr(reporting, "old_raw_diagnostics", lambda _: (old, {"status": "PASS"}))
    result = build_report(report, work, tmp_path / "old")
    assert result["confirmation_status"] == "BLOCKED_NO_INDEPENDENT_DATA"
    assert result["raw_rows"] == 0
    assert result["independent_test_numbers"] is None
    assert result["no_media_reads"] and result["no_training_or_inference"]
    assert json.loads((report / "selector_analysis.json").read_text()) == {}
    assert (report / "per_pair_results.csv").read_text().strip() == "status"
    first = {p.name: p.read_bytes() for p in report.iterdir()}
    assert build_report(report, work, tmp_path / "old") == result
    assert {p.name: p.read_bytes() for p in report.iterdir()} == first