"""The spread table finds the defects a fault count cannot see.""" from __future__ import annotations import json import tempfile import unittest from pathlib import Path from sds.spread import render, scan def decision(phase: str, phis: list[dict]) -> dict: return { "seat": "sds North", "seq": 1, "round": 1, "phase": phase, "actor": 0, "outcome": "answered", "candidates": [{"phi": p} for p in phis], } class TheSpreadFindsWhatAFaultCountCannot(unittest.TestCase): def write(self, rows: list[dict]) -> Path: run = Path(tempfile.mkdtemp()) (run / "a-sds_North.decisions.jsonl").write_text( "\n".join(json.dumps(r) for r in rows) + "\n" ) return run def test_a_column_that_never_varies_is_named(self): # `level_total_defense` was catalogued, weighted, and measured by # nobody. A column reporting one value everywhere is the same defect # seen from the data side. run = self.write([decision("MOVEMENT", [{"stuck": 0.5}, {"stuck": 0.5}])]) out = scan(run) self.assertEqual(out.by_phase["MOVEMENT"]["stuck"].low, 0.5) self.assertEqual(out.by_phase["MOVEMENT"]["stuck"].high, 0.5) self.assertIn("never varies", render(out)) def test_a_clamped_column_reads_as_pinned(self): # Four distance features clamped at 17 hexes on a 24-hex board, so # every reading past 17 was exactly 1.00. rows = [decision("MOVEMENT", [{"far": 1.0}, {"far": 1.0}, {"far": 0.4}])] out = scan(self.write(rows)) column = out.by_phase["MOVEMENT"]["far"] self.assertEqual(column.at_high, 2) self.assertIn("pinned", render(out)) def test_out_of_range_is_the_loudest_finding(self): rows = [decision("FIRING", [{"bad": 1.4}, {"bad": 0.2}])] out = scan(self.write(rows)) self.assertEqual(out.by_phase["FIRING"]["bad"].out_of_range, 1) text = render(out) self.assertIn("OUT OF RANGE", text) # It sorts first: a broken normalisation outranks everything else. body = [row for row in text.splitlines() if row.startswith("| `")] self.assertIn("bad", body[0]) def test_flat_within_a_decision_is_measured_per_decision(self): """The one no other report answers. A feature can move across a match and still be constant inside every decision, which makes it unfittable: the fit is difference-framed, so those rows are all zero for that column. """ rows = [ decision("MOVEMENT", [{"same": 0.2}, {"same": 0.2}]), decision("MOVEMENT", [{"same": 0.9}, {"same": 0.9}]), ] out = scan(self.write(rows)) column = out.by_phase["MOVEMENT"]["same"] self.assertNotEqual(column.low, column.high, "it moves across the run") self.assertEqual(column.flat_share, 1.0, "and never inside a decision") self.assertIn("flat within every decision", render(out)) def test_a_column_that_varies_is_not_flagged(self): rows = [decision("MOVEMENT", [{"good": 0.2}, {"good": 0.7}])] text = render(scan(self.write(rows))) for flag in ("OUT OF RANGE", "never varies", "flat within every", "pinned"): self.assertNotIn(flag, text) def test_phases_are_kept_apart(self): # A feature can be alive in firing and dead in movement; that is the # whole reason the firing-superset invariant exists. rows = [ decision("MOVEMENT", [{"heat": 0.0}, {"heat": 0.0}]), decision("FIRING", [{"heat": 0.1}, {"heat": 0.8}]), ] out = scan(self.write(rows)) self.assertEqual(out.by_phase["MOVEMENT"]["heat"].flat_share, 1.0) self.assertEqual(out.by_phase["FIRING"]["heat"].flat_share, 0.0) def test_a_pruned_run_says_so_rather_than_printing_an_empty_table(self): run = Path(tempfile.mkdtemp()) (run / "a.json").write_text(json.dumps({"players": []})) self.assertIn("No decision rows", render(scan(run))) if __name__ == "__main__": unittest.main() class AnIndicatorColumnIsNotAClamp(unittest.TestCase): """The six defence rungs are 0/1 by construction. Judging them by how often they sit on a bound would flag every one of them forever, and hide the columns where sitting on a bound is real evidence of a clamped denominator. """ def write(self, rows: list[dict]) -> Path: run = Path(tempfile.mkdtemp()) (run / "a-sds_North.decisions.jsonl").write_text( "\n".join(json.dumps(r) for r in rows) + "\n" ) return run def test_a_binary_column_is_reported_by_its_occupancy(self): rows = [decision("MOVEMENT", [{"defense_4plus": 0.0}] * 9 + [{"defense_4plus": 1.0}])] out = scan(self.write(rows)) column = out.by_phase["MOVEMENT"]["defense_4plus"] self.assertTrue(column.binary) text = render(out) self.assertIn("indicator, fires 10%", text) self.assertNotIn("**pinned**", text) def test_a_continuous_column_on_a_bound_is_still_a_clamp(self): rows = [decision("MOVEMENT", [{"far": 1.0}] * 8 + [{"far": 0.4}, {"far": 0.6}])] text = render(scan(self.write(rows))) self.assertIn("**pinned**", text) def test_negative_zero_is_not_shown_as_negative(self): # `incoming_damage` reports -0.0, which compares equal to 0.0. Printing # it as "-0.000" implies an out-of-range reading that is not there. rows = [decision("MOVEMENT", [{"z": -0.0}, {"z": 0.5}])] out = scan(self.write(rows)) self.assertEqual(out.by_phase["MOVEMENT"]["z"].out_of_range, 0) self.assertNotIn("-0.000", render(out))