"""AN4.1 lift table: cut on half A at control quartiles, held for half B."""
import pandas as pd, numpy as np, sys
f = sys.argv[1] if len(sys.argv) > 1 else "an41_rows.csv.gz"
df = pd.read_csv(f)
sub = sys.argv[2] if len(sys.argv) > 2 else "all"
for part in sub.split("+"):
    if part in ("v4", "v3p"): df = df[df.vg == part]
    elif part == "early": df = df[df.age_h < 1]
    elif part == "late": df = df[df.age_h >= 1]
matched = set(df[df.grp == "C"].wpool)
df = df[(df.grp == "C") | df.pool.isin(matched)]
if "dedup" in sub:  # one winner per cluster (funder root, else creator) per day, controls follow their winner
    import csv
    ROOT = {r["pool"].lower(): (r["root"] or r["creator"] or "").lower() for r in csv.DictReader(open(sys.argv[3] if len(sys.argv) > 3 else "crewlaunch.csv"))}
    w = df[df.grp == "W"].copy(); w["cl"] = [ROOT.get(p, p) + "|" + str(wh)[:10] for p, wh in zip(w.pool, w["when"])]
    keepw = set(w.drop_duplicates("cl").pool)
    df = df[df.wpool.isin(keepw)]
SKIP = {"grp", "wpool", "pool", "token", "venue", "vg", "t", "when", "half", "pulled_later", "best_1k", "x20", "hfam", "lp", "gross_max"}
num = [c for c in df.columns if c not in SKIP and pd.api.types.is_numeric_dtype(df[c])]
A = df[df.half == "A"]; B = df[df.half == "B"]
def share(d, m):
    w = d[d.grp == "W"]; c = d[d.grp == "C"]
    return (m[w.index].mean() if len(w) else np.nan, m[c.index].mean() if len(c) else np.nan, int(m[w.index].sum()), len(w), int(m[c.index].sum()), len(c))
out = []
for col in num:
    ca = A[A.grp == "C"][col].dropna()
    if ca.nunique() < 2: continue
    best = None
    for q in (0.1, 0.25, 0.5, 0.75, 0.9):
        cut = ca.quantile(q)
        for op in ("<=", ">="):
            mA = (A[col] <= cut) if op == "<=" else (A[col] >= cut)
            wA, cA, nwA, NwA, ncA, NcA = share(A, mA)
            if wA < 0.2 or not cA: continue
            lA = wA / cA
            if best is None or lA > best[0]: best = (lA, cut, op, wA, cA, nwA)
    if not best: continue
    lA, cut, op, wA, cA, nwA = best
    mB = (B[col] <= cut) if op == "<=" else (B[col] >= cut)
    wB, cB, nwB, NwB, ncB, NcB = share(B, mB)
    out.append(dict(feature=col, rule="%s %s %.4g" % (col, op, cut), liftA=round(lA, 2), liftB=round(wB / cB, 2) if cB else np.nan,
                    W_A=round(wA, 2), C_A=round(cA, 2), W_B=round(wB, 2), C_B=round(cB, 2), nW_A=nwA, nW_B=nwB))
for cat, lvl, n in [(c, l, n) for c in ("hfam", "lp") if c in df for l, n in df[c].value_counts().items()]:
    if n < 30: continue
    mA = A[cat] == lvl; mB = B[cat] == lvl
    wA, cA, nwA, *_ = share(A, mA); wB, cB, nwB, *_ = share(B, mB)
    out.append(dict(feature=cat, rule="%s == %s" % (cat, lvl), liftA=round(wA / cA, 2) if cA else np.nan, liftB=round(wB / cB, 2) if cB else np.nan,
                    W_A=round(wA, 2), C_A=round(cA, 2), W_B=round(wB, 2), C_B=round(cB, 2), nW_A=nwA, nW_B=nwB))
R = pd.DataFrame(out)
R["minlift"] = R[["liftA", "liftB"]].min(axis=1)
R["keep"] = (R.minlift >= 1.5) & (R.W_A >= 0.2) & (R.W_B >= 0.2) & (R.nW_A >= 10) & (R.nW_B >= 10)
R = R.sort_values("minlift", ascending=False)
pd.set_option("display.width", 250); pd.set_option("display.max_rows", 200)
print("rows", df.groupby(["grp", "half"]).size().to_dict())
print(R.to_string(index=False))
R.to_csv(f.replace("_rows.csv.gz", "_lift_%s.csv" % sub), index=False)
