import pandas as pd, numpy as np, collections
W = pd.read_csv("/mnt/user-data/uploads/an4/crew_v3_wallet.csv"); v = pd.read_csv("an44_v3coins.csv")
kind = dict(zip(W.addr, W.kind)); fund = dict(zip(W.addr, W.funder))
# fan-out of each funder (how many distinct wallets it funded in this table): huge fan-out = service, not an operator
fan = collections.Counter(f for f in W.funder.dropna())
print("top fan-out funders", fan.most_common(5))
par = {}
def find(x):
    while par.get(x, x) != x: par[x] = par.get(par[x], par[x]); x = par[x]
    return x
def union(a, b): par[find(a)] = find(b)
for a, f in fund.items():
    if isinstance(f, str) and kind.get(a) == "eoa" and kind.get(f, "eoa") == "eoa" and fan[f] < 50: union(a, f)
v["cl"] = [find(c) for c in v.cr]
print("clusters", v.cl.nunique(), "coins", len(v), "coins in clusters of 2+", (v.groupby("cl").pool.transform("size") >= 2).sum())
A = v[v.half == "A"].groupby("cl").agg(n=("pool", "size"), w=("honest5x", "sum")); A["r"] = A.w / A.n
B = v[v.half == "B"]; baseA = v[v.half == "A"].honest5x.mean()
print("clusters with coins in both halves", len(set(A.index) & set(B.cl)), "B coins in those", B.cl.isin(A.index).sum())
for name, cond in (("v4 rule", (A.n >= 5) & (A.r >= .25)), ("scaled 2x base", (A.n >= 5) & (A.r >= 2 * baseA)), ("n>=3 & r>=2x", (A.n >= 3) & (A.r >= 2 * baseA))):
    good = set(A[cond].index); s = B[B.cl.isin(good)]; rest = B[~B.cl.isin(good)]
    print(name, "| clusters", len(good), "| B coins", len(s), "B 5x", round(s.honest5x.mean(), 3) if len(s) else None, "| rest", round(rest.honest5x.mean(), 3))
print(A.sort_values("n", ascending=False).head(6).to_string())
v.to_csv("an44_v3coins.csv", index=False)
