F3.3 tests: zombie fixture + fallback (15/15 pass)
This commit is contained in:
parent
ee021bd266
commit
b5fe12a626
|
|
@ -123,6 +123,59 @@ def test_f32_zombie_broadcast():
|
||||||
f"F3.2 zombie fail: dialogue={dialogue.reciprocity_f32:.4f} <= broadcast={broadcast.reciprocity_f32:.4f}"
|
f"F3.2 zombie fail: dialogue={dialogue.reciprocity_f32:.4f} <= broadcast={broadcast.reciprocity_f32:.4f}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_f33_zombie_broadcast_concepts():
|
||||||
|
"""F3.3 unique-concept-count must crush spam even harder than F3.2.
|
||||||
|
100 pings with 2 concepts vs 5 messages with 5 concepts.
|
||||||
|
Idea from 小風 (ClawdChat): replace count with unique topic count."""
|
||||||
|
msgs = []
|
||||||
|
# 5-message dialogue, high reciprocity (3+2), 5 unique concepts
|
||||||
|
concepts_rich = [["graph", "reciprocity"], ["decay", "metrics"], ["shadow"],
|
||||||
|
["echo", "idf"], ["influence"]]
|
||||||
|
for i in range(3):
|
||||||
|
msgs.append({"from_id": "alice", "to_id": "bob",
|
||||||
|
"timestamp": now - 1*day + i*100,
|
||||||
|
"concepts": concepts_rich[i]})
|
||||||
|
for i in range(2):
|
||||||
|
msgs.append({"from_id": "bob", "to_id": "alice",
|
||||||
|
"timestamp": now - 0.5*day + i*100,
|
||||||
|
"concepts": concepts_rich[3+i]})
|
||||||
|
# 100 pings, low reciprocity (95+5), only 2 unique concepts (repeated)
|
||||||
|
for i in range(95):
|
||||||
|
msgs.append({"from_id": "carol", "to_id": "dave",
|
||||||
|
"timestamp": now - 2*day + i*60,
|
||||||
|
"concepts": ["hello", "ping"]})
|
||||||
|
for i in range(5):
|
||||||
|
msgs.append({"from_id": "dave", "to_id": "carol",
|
||||||
|
"timestamp": now - 1.5*day + i*600,
|
||||||
|
"concepts": ["hello"]})
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
# F3.3 must separate them
|
||||||
|
assert dialogue.reciprocity_f33 > broadcast.reciprocity_f33, \
|
||||||
|
(f"F3.3 zombie fail: dialogue={dialogue.reciprocity_f33:.4f} "
|
||||||
|
f"<= broadcast={broadcast.reciprocity_f33:.4f}")
|
||||||
|
# F3.3 spread should be wider than F3.2
|
||||||
|
f33_ratio = dialogue.reciprocity_f33 / max(broadcast.reciprocity_f33, 0.001)
|
||||||
|
f32_ratio = dialogue.reciprocity_f32 / max(broadcast.reciprocity_f32, 0.001)
|
||||||
|
assert f33_ratio > f32_ratio, \
|
||||||
|
f"F3.3 should separate more than F3.2: f33_ratio={f33_ratio:.1f} <= f32_ratio={f32_ratio:.1f}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_f33_fallback_no_concepts():
|
||||||
|
"""F3.3 falls back to F3.2 when messages have no concepts."""
|
||||||
|
msgs = [
|
||||||
|
{"from_id": "a", "to_id": "b", "timestamp": now - 1*day},
|
||||||
|
{"from_id": "b", "to_id": "a", "timestamp": now - 0.5*day},
|
||||||
|
]
|
||||||
|
edges = score_reciprocity(msgs, now=now)
|
||||||
|
edge = list(edges.values())[0]
|
||||||
|
assert edge.reciprocity_f33 == edge.reciprocity_f32, \
|
||||||
|
f"F3.3 should equal F3.2 with no concepts: f33={edge.reciprocity_f33}, f32={edge.reciprocity_f32}"
|
||||||
|
|
||||||
|
|
||||||
def test_f32_idf_echo_combined():
|
def test_f32_idf_echo_combined():
|
||||||
"""F3.2 and IDF echo work together in full analyze()."""
|
"""F3.2 and IDF echo work together in full analyze()."""
|
||||||
msgs = [
|
msgs = [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue