add ablation_sensitivity: volume → suppressor/amplifier bilateral test
This commit is contained in:
parent
c990a1ef97
commit
b88cfbdde1
|
|
@ -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:
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue