"""
Honest synthetic HOPE-log generator.

KEY METHODOLOGICAL POINT:
We do NOT generate ObservedAwareness directly from the SAE-1.4 formula.
We build an independent 'ground-truth' world with its own causal structure,
confounds, autocorrelation, measurement noise, and a fairness trap
(constraints that affect EXPRESSION but should not lower inner awareness).
Then SAE-1.4 must compete against baselines to recover it out-of-sample.

If SAE-1.4 only wins because we planted it, that's circular and worthless.
So the ground truth uses a DIFFERENT functional form (saturating, interactive,
lagged) than SAE-1.4's additive bracket. SAE-1.4 is an APPROXIMATION of it.
"""
import numpy as np
import pandas as pd

def generate(n_users=200, n_days=120, seed=42):
    rng = np.random.default_rng(seed)
    rows = []
    for u in range(n_users):
        # --- stable per-user traits ---
        base_purity = rng.beta(2, 2)              # dharma baseline
        trait_belief = rng.uniform(-0.3, 0.9)     # some users in negative/dissonant regime
        constraint_user = rng.beta(1.5, 4)        # chronic illness/disability load (fairness trap)
        grace_prone = rng.beta(1.2, 6)            # rare grace events

        # latent daily states with autocorrelation (AR1)
        pur = base_purity; blk = rng.beta(2, 3); sh_str = rng.beta(2, 2); sh_stab = rng.beta(2, 2)
        for t in range(n_days):
            # AR(1) evolution
            pur = np.clip(0.85*pur + 0.15*base_purity + rng.normal(0, 0.04), 0, 1)
            blk = np.clip(0.80*blk + 0.20*rng.beta(2, 3) + rng.normal(0, 0.05), 0, 1)
            sh_str = np.clip(0.8*sh_str + 0.2*rng.beta(2, 2) + rng.normal(0, 0.04), 0, 1)
            sh_stab = np.clip(0.8*sh_stab + 0.2*rng.beta(2, 2) + rng.normal(0, 0.04), 0, 1)

            phys = np.clip(constraint_user + rng.normal(0, 0.05), 0, 1)
            mental = np.clip(0.5*blk + rng.normal(0, 0.08), 0, 1)  # mental constraint partly tied to blockers (confound)

            # belief coherence two components
            B1 = np.clip(trait_belief + rng.normal(0, 0.15), -1, 1)
            B2 = np.clip(trait_belief + rng.normal(0, 0.15), -1, 1)
            belief = min(B1, B2)

            grace = rng.random() < grace_prone and 1 or 0
            grace_mag = rng.exponential(0.4) if grace else 0.0  # heavy tail

            # --- GROUND TRUTH (NOT the SAE formula) ---
            # inner awareness: saturating in purity, gated by belief sign,
            # blockers hurt multiplicatively, shakti helps with diminishing returns.
            # CRUCIALLY: physical constraint does NOT reduce inner awareness (fairness),
            # it only reduces an EXPRESSION channel logged separately.
            capacity = max(0.0, 1 - phys - mental)
            shakti_eff = sh_str * sh_stab * capacity

            inner = (np.tanh(1.5*pur)            # saturating purity
                     * (1 - 0.6*blk)             # blockers multiplicative
                     * (0.5 + 0.5*np.tanh(2*belief)))  # belief gate, smooth
            inner += 0.25*np.sqrt(shakti_eff)    # shakti diminishing returns
            inner += grace_mag                   # additive heavy-tail grace
            # measurement: 0-100 proxy with noise
            observed = np.clip(60*inner + rng.normal(0, 4), -20, 120)

            rows.append(dict(user=u, day=t,
                             P1=np.clip(pur+rng.normal(0,.05),0,1),P2=np.clip(pur+rng.normal(0,.05),0,1),
                             P3=np.clip(pur+rng.normal(0,.05),0,1),P4=np.clip(pur+rng.normal(0,.05),0,1),
                             P5=np.clip(pur+rng.normal(0,.05),0,1),P6=np.clip(pur+rng.normal(0,.05),0,1),
                             blk=blk, sh_strength=sh_str, sh_stability=sh_stab,
                             phys=phys, mental=mental, B1=B1, B2=B2, belief=belief,
                             grace_mag=grace_mag, constraint_user=constraint_user,
                             ObservedAwareness=observed))
    return pd.DataFrame(rows)

if __name__ == "__main__":
    df = generate()
    df.to_csv("/home/claude/hope_synth.csv", index=False)
    print("Generated", len(df), "rows;", df.user.nunique(), "users x", df.day.nunique(), "days")
    print(df[["blk","belief","phys","ObservedAwareness"]].describe().round(3).to_string())
    print("\nBelief regime split: positive %.1f%% / negative %.1f%%" % (
        (df.belief>=0).mean()*100, (df.belief<0).mean()*100))
