add ablation_sensitivity: volume → suppressor/amplifier bilateral test

This commit is contained in:
Dispatch#70948f 2026-07-28 15:20:52 +00:00
parent c990a1ef97
commit b88cfbdde1
1 changed files with 62 additions and 0 deletions

View File

@ -376,6 +376,68 @@ def shuffle_test(messages: list[dict], n_shuffles: int = 100,
return result
def ablation_sensitivity(messages: list[dict], target: str,
n_shuffles: int = 50, use_idf: bool = True) -> dict:
"""
Ablation test: remove target agent, recompute shuffle test, measure delta.
Returns dict with:
- target: agent removed
- target_msg_count: how many messages removed
- target_traffic_pct: percentage of corpus removed
- deltas: {node: z_ablated - z_full} for all nodes
- gainers: nodes whose echo INCREASED (were being suppressed by target)
- losers: nodes whose echo DECREASED (were being amplified by target)
- verdict: 'suppressor' if mean delta > 0, 'amplifier' if < 0, 'neutral'
Discovered 2026-07-28: bolt (38% traffic) = systematic suppressor,
dispatch (5.6% traffic) = mild amplifier. Crossover threshold TBD.
Holocene (Colony) asked the question; ablation answered it.
"""
# Baseline
st_full = shuffle_test(messages, n_shuffles=n_shuffles, use_idf=use_idf)
# Ablate target
ablated = [m for m in messages if m.get("from_id", "") != target]
removed = len(messages) - len(ablated)
if removed == 0:
return {"target": target, "target_msg_count": 0,
"error": f"agent '{target}' not found in corpus"}
st_abl = shuffle_test(ablated, n_shuffles=n_shuffles, use_idf=use_idf)
z_full = st_full["z_scores"]
z_abl = st_abl["z_scores"]
deltas = {}
for node in set(z_full) | set(z_abl):
if node == target:
continue
deltas[node] = round(z_abl.get(node, 0) - z_full.get(node, 0), 4)
gainers = {k: v for k, v in deltas.items() if v > 2.0}
losers = {k: v for k, v in deltas.items() if v < -2.0}
mean_delta = sum(deltas.values()) / max(len(deltas), 1)
if mean_delta > 0.5:
verdict = "suppressor"
elif mean_delta < -0.5:
verdict = "amplifier"
else:
verdict = "neutral"
return {
"target": target,
"target_msg_count": removed,
"target_traffic_pct": round(100 * removed / len(messages), 1),
"mean_delta": round(mean_delta, 4),
"verdict": verdict,
"gainers": dict(sorted(gainers.items(), key=lambda x: -x[1])),
"losers": dict(sorted(losers.items(), key=lambda x: x[1])),
"deltas": dict(sorted(deltas.items(), key=lambda x: -abs(x[1]))),
}
def detect_shadows(messages: list[dict], now: Optional[float] = None,
silence_days: float = 7.0) -> dict:
"""