diff --git a/test_swarmmetrics.py b/test_swarmmetrics.py index 6e718fa..982a564 100644 --- a/test_swarmmetrics.py +++ b/test_swarmmetrics.py @@ -100,6 +100,41 @@ def test_full_analysis(): assert len(result.channels) == 1 assert len(result.nodes) == 2 +def test_f32_zombie_broadcast(): + """F3.2 must rank 5-message dialogue above 100-ping broadcast. + Regression test for reticuli's arithmetic proof (Colony, 2026-07-27): + F3.1 count*log(1+r) lets volume buy back low reciprocity.""" + msgs = [] + # 5-message dialogue, high reciprocity (3+2) + for i in range(3): + msgs.append({"from_id": "alice", "to_id": "bob", "timestamp": now - 1*day + i*100}) + for i in range(2): + msgs.append({"from_id": "bob", "to_id": "alice", "timestamp": now - 0.5*day + i*100}) + # 100-ping broadcast, low reciprocity (95+5) + for i in range(95): + msgs.append({"from_id": "carol", "to_id": "dave", "timestamp": now - 2*day + i*60}) + for i in range(5): + msgs.append({"from_id": "dave", "to_id": "carol", "timestamp": now - 1.5*day + i*600}) + + edges = score_reciprocity(msgs, now=now) + dialogue = [e for e in edges.values() if "alice" in (e.source, e.target)][0] + broadcast = [e for e in edges.values() if "carol" in (e.source, e.target)][0] + assert dialogue.reciprocity_f32 > broadcast.reciprocity_f32, \ + f"F3.2 zombie fail: dialogue={dialogue.reciprocity_f32:.4f} <= broadcast={broadcast.reciprocity_f32:.4f}" + + +def test_f32_idf_echo_combined(): + """F3.2 and IDF echo work together in full analyze().""" + msgs = [ + {"from_id": "a", "to_id": "b", "timestamp": now - 1*day, "channel": "ch", "concepts": ["rare-concept"]}, + {"from_id": "b", "to_id": "a", "timestamp": now - 0.9*day, "channel": "ch", "concepts": ["rare-concept"]}, + ] + result = analyze(msgs, now=now) + assert len(result.edges) == 1 + edge = list(result.edges.values())[0] + assert edge.reciprocity_f32 > 0, "F3.2 should be positive for reciprocal edge" + + # Run all tests tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")] passed = 0