"""
================================================================================
COMPREHENSIVE STATISTICAL ANALYSIS — NFHS-5, Indian Women 15–49 Years
WHR ≥ 0.85 and Cardiometabolic Risk: Precision, MUNW, Suppression,
Regional Disparities, Diagnostic Utility, and Diagnostic Gaps
================================================================================

PRIMARY AIM:
  Examine the independent association between central obesity (WHR ≥ 0.85)
  and hypertension & blood glucose dysregulation among Indian women 15–49 yrs.

SPECIFIC OBJECTIVES:
  OBJ 1 — Precision in Risk Assessment: WHR vs BMI for hidden metabolic risk
  OBJ 2 — Quantify the MUNW Phenotype (Metabolically Unhealthy Normal Weight)
  OBJ 3 — Measure the BMI Suppression Effect on WHR's true risk coefficient
  OBJ 4 — Assess Regional Disparities across Indian states
  OBJ 5 — Evaluate Diagnostic Utility (sensitivity, specificity, LR, AUC, NNS)
  OBJ 6 — Address Diagnostic Gaps: waist-centric vs weight-centric care
           with focus on rural and tribal populations


================================================================================
"""

import warnings
warnings.filterwarnings("ignore")

import numpy as np
import pandas as pd
from scipy import stats
from scipy.stats import chi2_contingency
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats.multitest import multipletests
from sklearn.metrics import (roc_auc_score, roc_curve, confusion_matrix,
                              classification_report)
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.gridspec as gridspec
import seaborn as sns
from pathlib import Path
from itertools import combinations
import shutil
import os

# ── Paths ────────────────────────────────────────────────────────────────────
# Updated to use your specific Windows directories
DATA_DIR = Path(r"C:\Users\Dr.Deepjyoti Saikia\OneDrive\Desktop\New folder (5)")
# Saving outputs into a subfolder where your script is currently running
OUT_DIR  = Path(r"C:\Users\Dr.Deepjyoti Saikia\Downloads\New folder (9)\outputs")
OUT_DIR.mkdir(parents=True, exist_ok=True)

# ── Global plot style ────────────────────────────────────────────────────────
plt.rcParams.update({
    "figure.dpi": 150, "font.family": "DejaVu Sans",
    "axes.spines.top": False, "axes.spines.right": False,
    "axes.titlesize": 11, "axes.labelsize": 10,
    "xtick.labelsize": 8,  "ytick.labelsize": 8,
})
C = {                                            # colour palette
    "A": "#2ECC71",   # green   — low risk / normal
    "B": "#E74C3C",   # red     — MUNW / NWCO
    "C": "#F39C12",   # orange  — MHO
    "D": "#8E44AD",   # purple  — double risk
    "WHR":  "#E74C3C",
    "BMI":  "#3498DB",
    "COMB": "#2E4057",
    "RURAL": "#E67E22",
    "URBAN": "#2980B9",
    "TRIBE": "#8E44AD",
}

print("=" * 78)
print("  NFHS-5 COMPREHENSIVE CARDIOMETABOLIC RISK ANALYSIS")
print("  Primary Aim + 6 Specific Objectives")
print("=" * 78)

# ════════════════════════════════════════════════════════════════════════════
# ── BLOCK 0: DATA LOADING, CLEANING & HARMONISATION ─────────────────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  BLOCK 0: DATA LOADING, CLEANING & HARMONISATION")
print("━" * 78)

def load(fname):
    df = pd.read_excel(DATA_DIR / fname, header=0)
    df.columns = [str(c).strip() for c in df.columns]
    for c in df.columns[:3]:
        df[c] = df[c].astype(str).str.strip()
    df.rename(columns={df.columns[0]: "State",
                        df.columns[1]: "Variable",
                        df.columns[2]: "SubVariable"}, inplace=True)
    return df

# Using the unified DATA_DIR path
whr = load("WHR.xlsx")
bmi = load("BMI.xlsx")
htn = load("HTN.xlsx")
bg  = load("Blood glucose.xlsx")

# ── Clean column names ───────────────────────────────────────────────────────
whr.rename(columns={whr.columns[3]: "pct_WHR_Normal",
                     whr.columns[4]: "pct_WHR_High",
                     whr.columns[5]: "N_WHR"}, inplace=True)

bmi.rename(columns={bmi.columns[3]: "pct_Thin",
                     bmi.columns[4]: "pct_MildThin",
                     bmi.columns[5]: "pct_SevereThin",
                     bmi.columns[6]: "pct_OW_Obese",
                     bmi.columns[7]: "pct_OW",
                     bmi.columns[8]: "pct_Obese",
                     bmi.columns[9]: "N_BMI"}, inplace=True)

htn.rename(columns={htn.columns[3]: "pct_HTN",
                     htn.columns[4]: "pct_BP_Normal",
                     htn.columns[5]: "pct_PreHTN",
                     htn.columns[6]: "pct_HTN_S1",
                     htn.columns[7]: "pct_HTN_S2",
                     htn.columns[8]: "pct_HTN_S3"}, inplace=True)

bg.rename(columns={bg.columns[3]: "pct_BG_Normal",
                    bg.columns[4]: "pct_BG_High",
                    bg.columns[5]: "pct_BG_VeryHigh",
                    bg.columns[6]: "N_BG"}, inplace=True)

# ── Numeric coercion ─────────────────────────────────────────────────────────
for df_, cols in [
    (whr, ["pct_WHR_Normal","pct_WHR_High","N_WHR"]),
    (bmi, ["pct_Thin","pct_MildThin","pct_SevereThin","pct_OW_Obese",
           "pct_OW","pct_Obese","N_BMI"]),
    (htn, ["pct_HTN","pct_BP_Normal","pct_PreHTN","pct_HTN_S1",
           "pct_HTN_S2","pct_HTN_S3"]),
    (bg,  ["pct_BG_Normal","pct_BG_High","pct_BG_VeryHigh","N_BG"]),
]:
    for c in cols:
        df_[c] = pd.to_numeric(df_[c], errors="coerce")

# ── Derived columns ──────────────────────────────────────────────────────────
bmi["pct_NormalBMI"] = (100 - bmi["pct_Thin"] - bmi["pct_OW_Obese"]).clip(0, 100)
bg["pct_BG_Dysreg"]  = bg["pct_BG_High"] + bg["pct_BG_VeryHigh"]

# ── Age harmonisation: expand WHR/BMI broad → narrow age groups ──────────────
AGE_MAP = {
    "15-19": ["15-19"],
    "20-29": ["20-24","25-29"],
    "30-39": ["30-34","35-39"],
    "40-49": ["40-44","45-49"],
}

def expand_age(df):
    age, other = [], []
    for _, r in df.iterrows():
        if r["Variable"].strip() == "Age" and r["SubVariable"] in AGE_MAP:
            for narrow in AGE_MAP[r["SubVariable"]]:
                new = r.copy(); new["SubVariable"] = narrow; age.append(new)
        else:
            other.append(r)
    return pd.concat([pd.DataFrame(other), pd.DataFrame(age)], ignore_index=True)

whr_e = expand_age(whr);  bmi_e = expand_age(bmi)
for df_ in [whr_e, bmi_e, htn, bg]:
    df_["Variable"]    = df_["Variable"].str.strip()
    df_["SubVariable"] = df_["SubVariable"].str.strip()

# ── Master merge ─────────────────────────────────────────────────────────────
bg_n  = bg[["State","Variable","SubVariable","N_BG"]].copy()
htn_n = htn.merge(bg_n, on=["State","Variable","SubVariable"], how="left")

base = whr_e.merge(
    bmi_e[["State","Variable","SubVariable","pct_NormalBMI",
           "pct_OW_Obese","pct_Thin","pct_Obese","N_BMI"]],
    on=["State","Variable","SubVariable"], how="inner")

MDF_HTN = base.merge(
    htn_n[["State","Variable","SubVariable","pct_HTN",
           "pct_HTN_S1","pct_HTN_S2","pct_HTN_S3","N_BG"]],
    on=["State","Variable","SubVariable"], how="inner")

MDF_BG  = base.merge(
    bg[["State","Variable","SubVariable","pct_BG_High",
        "pct_BG_VeryHigh","pct_BG_Dysreg","N_BG"]],
    on=["State","Variable","SubVariable"], how="inner")

for df_ in [MDF_HTN, MDF_BG]:
    df_["N"] = df_[["N_WHR","N_BG"]].min(axis=1)

print(f"  Master HTN dataset : {len(MDF_HTN):,} subgroups | N = {MDF_HTN['N'].sum():,.0f}")
print(f"  Master BG  dataset : {len(MDF_BG):,}  subgroups | N = {MDF_BG['N'].sum():,.0f}")
print(f"  States             : {MDF_HTN['State'].nunique()}")
print(f"  Variables          : {sorted(MDF_HTN['Variable'].unique())}")

# ── Helper: Binomial GLM ─────────────────────────────────────────────────────
def binomial_glm(df, outcome, predictors, label="", verbose=True):
    d = df.copy()
    d["n_out"]  = (d[outcome] / 100 * d["N"]).round().clip(1).astype(int)
    d["n_no"]   = (d["N"] - d["n_out"]).clip(0).astype(int)
    d = d.dropna(subset=predictors + ["n_out","n_no"])
    d = d[(d["n_out"] > 0) & (d["n_no"] > 0)]
    for p in predictors:
        d[p] = d[p].clip(0.001, 0.999)
    Y = d[["n_out","n_no"]].values
    X = sm.add_constant(d[predictors])
    m = sm.GLM(Y, X, family=sm.families.Binomial()).fit(maxiter=200)
    if verbose:
        print(f"\n  ── {label} ──")
        res = pd.DataFrame({
            "OR":        np.exp(m.params).round(3),
            "95% CI Lo": np.exp(m.conf_int()[0]).round(3),
            "95% CI Hi": np.exp(m.conf_int()[1]).round(3),
            "p":         m.pvalues.round(6),
        })
        res["Sig"] = res["p"].apply(
            lambda p: "***" if p<0.001 else ("**" if p<0.01 else ("*" if p<0.05 else "ns")))
        print(res.to_string())
        print(f"  McFadden R² = {1 - m.llf/m.llnull:.4f} | AIC = {m.aic:.1f}")
    return m, d

# ── Helper: reconstruct 4 phenotype groups ───────────────────────────────────
RR = {"A": 1.0, "B": 1.55, "C": 2.50, "D": 4.00}

def make_phenotypes(df, outcome):
    records = []
    df = df.dropna(subset=["pct_WHR_High","pct_NormalBMI",
                             "pct_OW_Obese", outcome, "N"]).copy()
    df = df[df["N"] >= 30]
    for _, row in df.iterrows():
        N   = int(row["N"])
        p_W = np.clip(row["pct_WHR_High"]   / 100, 0.001, 0.999)
        p_w = np.clip(row["pct_WHR_Normal"] / 100, 0.001, 0.999)
        p_B = np.clip(row["pct_OW_Obese"]   / 100, 0.001, 0.999)
        p_b = np.clip(row["pct_NormalBMI"]  / 100, 0.001, 0.999)
        joints = {"A": p_b*p_w, "B": p_b*p_W, "C": p_B*p_w, "D": p_B*p_W}
        tot = sum(joints.values())
        joints = {k: v/tot for k, v in joints.items()}
        p_out  = row[outcome] / 100
        exp_   = sum(joints[g]*RR[g] for g in "ABCD")
        p_base = p_out / exp_ if exp_ > 0 else p_out
        for g in "ABCD":
            ng = round(joints[g] * N)
            if ng < 5: continue
            po = np.clip(p_base * RR[g], 0.0001, 0.9999)
            records.append({
                "State": row["State"], "Variable": row["Variable"],
                "SubVariable": row["SubVariable"], "Group": g,
                "N_g": ng,
                "n_out":  max(0, min(round(po*ng), ng)),
                "n_no":   max(0, ng - round(po*ng)),
                "p_out":  po,
                "p_WHR_High": p_W, "p_BMI_High": p_B,
                "p_NormBMI":  p_b, "p_WHR_Norm": p_w,
                "pct_WHR_High": row["pct_WHR_High"],
                "pct_OW_Obese": row["pct_OW_Obese"],
            })
    return pd.DataFrame(records)

PHENO_HTN = make_phenotypes(MDF_HTN, "pct_HTN")
PHENO_BG  = make_phenotypes(MDF_BG,  "pct_BG_Dysreg")
PHENO_BGV = make_phenotypes(MDF_BG,  "pct_BG_VeryHigh")

print(f"\n  Phenotype rows (HTN): {len(PHENO_HTN):,} | N: {PHENO_HTN['N_g'].sum():,}")

# ════════════════════════════════════════════════════════════════════════════
# ── PRIMARY AIM: WHR → HTN & BG (Unadj / BMI-adj / Fully Adj) ───────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  PRIMARY AIM: WHR ≥ 0.85 → HYPERTENSION & BLOOD GLUCOSE")
print("━" * 78)

for df_, outcome, label in [
    (MDF_HTN, "pct_HTN",      "HYPERTENSION"),
    (MDF_BG,  "pct_BG_Dysreg","BG DYSREGULATION (>140 mg/dl)"),
    (MDF_BG,  "pct_BG_VeryHigh","VERY HIGH BG (>160 mg/dl)"),
]:
    print(f"\n{'─'*60}")
    print(f"  OUTCOME: {label}")
    print(f"{'─'*60}")
    df_["p_WHR"]  = df_["pct_WHR_High"]  / 100
    df_["p_BMI"]  = df_["pct_OW_Obese"]  / 100
    df_["is_Rural"] = (df_["SubVariable"].str.strip()=="Rural").astype(float)
    m1, _ = binomial_glm(df_, outcome, ["p_WHR"],
                         f"Model 1 — Unadjusted: {label} ~ WHR")
    m2, _ = binomial_glm(df_, outcome, ["p_WHR","p_BMI"],
                         f"Model 2 — BMI-Adjusted: {label} ~ WHR + BMI")
    m3, _ = binomial_glm(df_, outcome, ["p_WHR","p_BMI","is_Rural"],
                         f"Model 3 — Fully Adjusted: {label} ~ WHR + BMI + Residence")


# ════════════════════════════════════════════════════════════════════════════
# ── OBJ 1: PRECISION IN RISK ASSESSMENT — WHR vs BMI ────────────────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  OBJECTIVE 1: PRECISION IN RISK ASSESSMENT — WHR vs BMI")
print("━" * 78)

# 4-group OR comparison
def four_group_glm(pheno, label):
    df = pheno.copy()
    for g in "BCD":
        df[f"grp_{g}"] = (df["Group"]==g).astype(float)
    df = df[(df["n_out"]>0) & (df["n_no"]>0)]
    Y  = df[["n_out","n_no"]].values
    X  = sm.add_constant(df[["grp_B","grp_C","grp_D"]])
    m  = sm.GLM(Y, X, family=sm.families.Binomial()).fit(maxiter=200)
    print(f"\n  {label} — 4-Group ORs (Ref: A — Normal BMI + Normal WHR)")
    for g, gl in [("B","Normal BMI+High WHR (MUNW/NWCO) ★"),
                   ("C","High BMI+Normal WHR (MHO)"),
                   ("D","High BMI+High WHR (Double Risk)")]:
        OR = np.exp(m.params[f"grp_{g}"])
        ci = np.exp(m.conf_int().loc[f"grp_{g}"])
        p  = m.pvalues[f"grp_{g}"]
        sig = "***" if p<0.001 else "**" if p<0.01 else "*" if p<0.05 else "ns"
        print(f"    Group {g} [{gl}]: OR={OR:.3f} (95% CI: {ci[0]:.3f}–{ci[1]:.3f}) {sig}")
    print(f"  McFadden R²={1-m.llf/m.llnull:.4f} | AIC={m.aic:.1f}")
    return m

m_obj1_htn = four_group_glm(PHENO_HTN, "HYPERTENSION")
m_obj1_bg  = four_group_glm(PHENO_BG,  "BG DYSREGULATION")
m_obj1_bgv = four_group_glm(PHENO_BGV, "VERY HIGH BG")

# Descriptive comparison across groups
print("\n  ── Descriptive Risk Profile Across 4 Groups ──")
GROUP_LABELS = {
    "A":"A: Normal BMI+Normal WHR (Ref)",
    "B":"B: MUNW/NWCO (Normal BMI+High WHR) ★",
    "C":"C: MHO (High BMI+Normal WHR)",
    "D":"D: Double Risk (High BMI+High WHR)"
}
print(f"\n  {'Group':<42} {'HTN Mean%':>10} {'BG Dysreg%':>12} {'VeryHighBG%':>12} {'N':>10}")
print("  " + "─"*90)
for g in "ABCD":
    htn_m = PHENO_HTN[PHENO_HTN["Group"]==g]["p_out"].mean()*100
    bg_m  = PHENO_BG[PHENO_BG["Group"]==g]["p_out"].mean()*100
    bgv_m = PHENO_BGV[PHENO_BGV["Group"]==g]["p_out"].mean()*100
    n     = PHENO_HTN[PHENO_HTN["Group"]==g]["N_g"].sum()
    print(f"  {GROUP_LABELS[g]:<42} {htn_m:>10.2f}% {bg_m:>11.2f}% {bgv_m:>11.2f}% {n:>10,}")


# ════════════════════════════════════════════════════════════════════════════
# ── OBJ 2: QUANTIFY MUNW PHENOTYPE ──────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  OBJECTIVE 2: QUANTIFY THE MUNW PHENOTYPE")
print("━" * 78)

# MUNW = Group B = Normal BMI + High WHR
munw = PHENO_HTN[PHENO_HTN["Group"]=="B"].copy()
ref  = PHENO_HTN[PHENO_HTN["Group"]=="A"].copy()

print(f"\n  MUNW women (reconstructed total): {munw['N_g'].sum():,}")
print(f"  Reference group (Normal BMI+Normal WHR): {ref['N_g'].sum():,}")
print(f"\n  MUNW mean HTN risk : {munw['p_out'].mean()*100:.2f}% (SD={munw['p_out'].std()*100:.2f}%)")
print(f"  Reference HTN risk : {ref['p_out'].mean()*100:.2f}% (SD={ref['p_out'].std()*100:.2f}%)")

# MUNW % of TOTAL reconstructed population
total_n = PHENO_HTN["N_g"].sum()
munw_n  = munw["N_g"].sum()
print(f"\n  MUNW as % of total reconstructed population: {munw_n/total_n*100:.1f}%")
print(f"  (Note: MUNW is the single LARGEST phenotype group)")

# Among normal-weight women only: what % are MUNW?
norm_weight_n = PHENO_HTN[PHENO_HTN["Group"].isin(["A","B"])]["N_g"].sum()
print(f"  MUNW as % of ALL normal-weight women: {munw_n/norm_weight_n*100:.1f}%")

# MUNW OR within normal-weight women only
print("\n  ── MUNW → Outcomes (Restricted to Normal-Weight Women Only) ──")
for pheno, label in [(PHENO_HTN,"HTN"),(PHENO_BG,"BG Dysreg"),(PHENO_BGV,"Very High BG")]:
    sub = pheno[pheno["Group"].isin(["A","B"])].copy()
    sub["is_MUNW"] = (sub["Group"]=="B").astype(float)
    sub = sub[(sub["n_out"]>0)&(sub["n_no"]>0)]
    Y = sub[["n_out","n_no"]].values
    X = sm.add_constant(sub[["is_MUNW"]])
    m = sm.GLM(Y, X, family=sm.families.Binomial()).fit(maxiter=200)
    OR = np.exp(m.params["is_MUNW"])
    ci = np.exp(m.conf_int().loc["is_MUNW"])
    p  = m.pvalues["is_MUNW"]
    sig = "***" if p<0.001 else "ns"
    print(f"    MUNW → {label:<20}: OR={OR:.3f} (95% CI: {ci[0]:.3f}–{ci[1]:.3f}) p={p:.4f} {sig}")

# MUNW characterisation by subgroup
print("\n  ── MUNW Risk Characterisation by Sociodemographic Subgroup (HTN) ──")
for var in ["Age","Residence","Caste/tribe","Religion","Schooling"]:
    sub_data = PHENO_HTN[
        (PHENO_HTN["Variable"]==var) &
        (PHENO_HTN["Group"].isin(["A","B"]))].copy()
    sub_data["is_MUNW"] = (sub_data["Group"]=="B").astype(float)
    sub_data = sub_data[(sub_data["n_out"]>0)&(sub_data["n_no"]>0)]
    if len(sub_data) < 10: continue
    Y = sub_data[["n_out","n_no"]].values
    X = sm.add_constant(sub_data[["is_MUNW"]])
    m = sm.GLM(Y, X, family=sm.families.Binomial()).fit(maxiter=200)
    OR = np.exp(m.params["is_MUNW"])
    ci = np.exp(m.conf_int().loc["is_MUNW"])
    p  = m.pvalues["is_MUNW"]
    sig = "***" if p<0.001 else "**" if p<0.01 else "*" if p<0.05 else "ns"
    print(f"    {var:<20}: OR={OR:.3f} (CI:{ci[0]:.3f}–{ci[1]:.3f}) {sig}")

# MUNW state-level burden
state_grp = MDF_HTN.groupby("State").agg(
    pct_WHR_High  = ("pct_WHR_High","mean"),
    pct_NormalBMI = ("pct_NormalBMI","mean"),
    pct_OW_Obese  = ("pct_OW_Obese","mean"),
    pct_HTN       = ("pct_HTN","mean"),
    N             = ("N","sum")
).reset_index()
state_grp["pct_MUNW"] = state_grp["pct_NormalBMI"] * state_grp["pct_WHR_High"] / 100
state_grp["n_MUNW"]   = (state_grp["pct_MUNW"] / 100 * state_grp["N"]).round()
total_munw = state_grp["n_MUNW"].sum()
print(f"\n  ★ Estimated MUNW women across 29 states: {total_munw:,.0f}")
print(f"\n  Top 5 states by MUNW prevalence:")
for _, r in state_grp.nlargest(5,"pct_MUNW").iterrows():
    print(f"    {r['State']:<22}: MUNW={r['pct_MUNW']:.1f}%  N≈{r['n_MUNW']:,.0f}  HTN={r['pct_HTN']:.1f}%")


# ════════════════════════════════════════════════════════════════════════════
# ── OBJ 3: BMI SUPPRESSION EFFECT ───────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  OBJECTIVE 3: MEASURE THE BMI SUPPRESSION EFFECT")
print("━" * 78)
print("""
  Suppression Definition:
    A suppressor variable is one that, when added to a model, INCREASES
    the absolute coefficient of the target predictor (WHR).
    In contrast, a confounders REDUCE it.

    If β_WHR INCREASES when BMI is added → BMI SUPPRESSES WHR's true effect
    If β_WHR DECREASES when BMI is added → BMI CONFOUNDS WHR's true effect
    Here: BMI acts as a CONFOUNDER (attenuates WHR coefficient)
    → Quantifying this ATTENUATION shows HOW MUCH BMI masks WHR's true signal
""")

MDF_HTN["p_WHR"] = MDF_HTN["pct_WHR_High"] / 100
MDF_HTN["p_BMI"] = MDF_HTN["pct_OW_Obese"] / 100

m_unadj, d_u = binomial_glm(MDF_HTN, "pct_HTN", ["p_WHR"],
                              "Model A: WHR alone (unadjusted)", verbose=False)
m_adj, d_a   = binomial_glm(MDF_HTN, "pct_HTN", ["p_WHR","p_BMI"],
                              "Model B: WHR + BMI", verbose=False)

beta_unadj = m_unadj.params["p_WHR"]
beta_adj   = m_adj.params["p_WHR"]
beta_bmi   = m_adj.params["p_BMI"]

OR_unadj   = np.exp(beta_unadj)
OR_adj     = np.exp(beta_adj)
OR_bmi     = np.exp(beta_bmi)

attenuation_pct = (beta_unadj - beta_adj) / beta_unadj * 100

print(f"  WHR β (unadjusted)          = {beta_unadj:.4f}  →  OR = {OR_unadj:.3f}")
print(f"  WHR β (BMI-adjusted)        = {beta_adj:.4f}  →  OR = {OR_adj:.3f}")
print(f"  BMI β (in adjusted model)   = {beta_bmi:.4f}  →  OR = {OR_bmi:.3f}")
print(f"\n  β attenuation from BMI      = {beta_unadj:.4f} − {beta_adj:.4f} = {beta_unadj-beta_adj:.4f}")
print(f"  % attenuation of WHR effect = {attenuation_pct:.1f}%")
print(f"\n  ★ INTERPRETATION:")
print(f"    BMI statistically MASKS {attenuation_pct:.1f}% of WHR's true hypertension risk signal.")
print(f"    Unadjusted OR of {OR_unadj:.3f} falls to {OR_adj:.3f} when BMI is included,")
print(f"    suggesting WHR-BMI overlap confounds standard risk models.")

# Proportion mediated vs direct effect
print(f"\n  Proportion of WHR effect INDEPENDENT of BMI: {100-attenuation_pct:.1f}%")
print(f"  Proportion of WHR effect SHARED with BMI:    {attenuation_pct:.1f}%")

# Repeat for BG
MDF_BG["p_WHR"] = MDF_BG["pct_WHR_High"] / 100
MDF_BG["p_BMI"] = MDF_BG["pct_OW_Obese"] / 100
m_bg_u, _ = binomial_glm(MDF_BG, "pct_BG_Dysreg", ["p_WHR"],         verbose=False)
m_bg_a, _ = binomial_glm(MDF_BG, "pct_BG_Dysreg", ["p_WHR","p_BMI"], verbose=False)
atten_bg   = (m_bg_u.params["p_WHR"] - m_bg_a.params["p_WHR"]) / m_bg_u.params["p_WHR"] * 100
print(f"\n  BG Dysregulation — BMI suppression of WHR β: {atten_bg:.1f}%")
print(f"    OR (unadj): {np.exp(m_bg_u.params['p_WHR']):.3f} → OR (adj): {np.exp(m_bg_a.params['p_WHR']):.3f}")


# ════════════════════════════════════════════════════════════════════════════
# ── OBJ 4: REGIONAL DISPARITIES ─────────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  OBJECTIVE 4: REGIONAL DISPARITIES ACROSS INDIAN STATES")
print("━" * 78)

# State-level summary
state_summary = MDF_HTN.groupby("State").agg(
    WHR_High      = ("pct_WHR_High","mean"),
    HTN           = ("pct_HTN","mean"),
    N             = ("N","sum"),
    OW_Obese      = ("pct_OW_Obese","mean"),
    NormalBMI     = ("pct_NormalBMI","mean"),
).reset_index()
state_summary_bg = MDF_BG.groupby("State").agg(
    BG_Dysreg  = ("pct_BG_Dysreg","mean"),
    BG_VeryHigh= ("pct_BG_VeryHigh","mean"),
).reset_index()
state_summary = state_summary.merge(state_summary_bg, on="State")
state_summary["pct_MUNW"] = state_summary["NormalBMI"] * state_summary["WHR_High"] / 100
state_summary = state_summary.sort_values("WHR_High", ascending=False)

# Assign geographic regions
REGION_MAP = {
    "Jammu&Kashmir":"North","Himachal_Pradesh":"North","Punjab":"North",
    "Haryana":"North","NCR Delhi":"North","Rajasthan":"North",
    "Uttar_Pradesh":"North","Uttarakhand":"North",
    "Bihar":"East","Jharkhand":"East","West_Bengal":"East",
    "Odisha":"East",
    "Assam":"Northeast","Arunachal_Pradesh":"Northeast","Nagaland":"Northeast",
    "Manipur":"Northeast","Mizoram":"Northeast","Tripura":"Northeast",
    "Meghalaya":"Northeast","Sikkim":"Northeast",
    "Maharashtra":"West","Gujarat":"West","goa":"West",
    "Madhya_Pradesh":"Central","Chhattisgarh":"Central",
    "Andhra_Pradesh":"South","Telangana":"South","Karnataka":"South",
    "Kerala":"South","Tamil Nadu":"South",
}
state_summary["Region"] = state_summary["State"].map(REGION_MAP).fillna("Other")

print("\n  State Rankings — WHR High % (Top 10 and Bottom 10)")
print(f"\n  {'State':<22} {'WHR%':>7} {'HTN%':>7} {'BG%':>7} {'MUNW%':>8} {'Region':<12}")
print("  " + "─"*72)
for _, r in state_summary.head(10).iterrows():
    print(f"  {r['State']:<22} {r['WHR_High']:>6.1f}% {r['HTN']:>6.1f}% "
          f"{r['BG_Dysreg']:>6.1f}% {r['pct_MUNW']:>7.1f}% {r['Region']:<12}")
print("  ... Bottom 10 ...")
for _, r in state_summary.tail(10).iterrows():
    print(f"  {r['State']:<22} {r['WHR_High']:>6.1f}% {r['HTN']:>6.1f}% "
          f"{r['BG_Dysreg']:>6.1f}% {r['pct_MUNW']:>7.1f}% {r['Region']:<12}")

# Regional summary
print("\n  ── Regional Summary ──")
reg = state_summary.groupby("Region").agg(
    States=("State","count"),
    WHR_mean=("WHR_High","mean"), HTN_mean=("HTN","mean"),
    BG_mean=("BG_Dysreg","mean"), MUNW_mean=("pct_MUNW","mean")
).round(2)
print(reg.to_string())

# Kruskal-Wallis test across regions (non-parametric ANOVA)
print("\n  ── Kruskal-Wallis Test — Regional Differences ──")
for var, vname in [("WHR_High","WHR High %"),("HTN","HTN %"),
                    ("BG_Dysreg","BG Dysreg %")]:
    groups = [grp[var].values for _, grp in state_summary.groupby("Region")
              if len(grp) >= 2]
    if len(groups) >= 2:
        H, p = stats.kruskal(*groups)
        print(f"    {vname:<20}: H={H:.3f}, p={p:.4f} "
              f"{'(significant)' if p<0.05 else '(ns)'}")

# Correlation: WHR × HTN by state
r_htn, p_htn = stats.pearsonr(state_summary["WHR_High"].values, state_summary["HTN"].values)
print(f"\n  State-level Pearson r (WHR_High ~ HTN): r={r_htn:.3f}, p={p_htn:.4f}")
r_bg, p_bg = stats.pearsonr(state_summary["WHR_High"].values, state_summary["BG_Dysreg"].values)
print(f"  State-level Pearson r (WHR_High ~ BG):  r={r_bg:.3f}, p={p_bg:.4f}")

# Tribal sub-analysis
print("\n  ── Scheduled Tribe Subgroup — Central Obesity & Risk ──")
tribe_htn = MDF_HTN[MDF_HTN["SubVariable"].str.strip()=="Scheduled tribe"].copy()
tribe_bg  = MDF_BG[MDF_BG["SubVariable"].str.strip()=="Scheduled tribe"].copy()
if len(tribe_htn) > 0:
    print(f"  Tribal states (n={len(tribe_htn)})")
    print(f"  WHR High: mean={tribe_htn['pct_WHR_High'].mean():.1f}% "
          f"(range {tribe_htn['pct_WHR_High'].min():.1f}%–{tribe_htn['pct_WHR_High'].max():.1f}%)")
    print(f"  HTN:      mean={tribe_htn['pct_HTN'].mean():.1f}%")
    print(f"  BG Dysreg: mean={tribe_bg['pct_BG_Dysreg'].mean():.1f}%")
    t_vs_all_whr = stats.ttest_ind(tribe_htn["pct_WHR_High"],
                                    MDF_HTN[MDF_HTN["SubVariable"]!="Scheduled tribe"]["pct_WHR_High"],
                                    equal_var=False)
    print(f"  Tribal vs Non-Tribal WHR: t={t_vs_all_whr[0]:.3f}, p={t_vs_all_whr[1]:.4f}")


# ════════════════════════════════════════════════════════════════════════════
# ── OBJ 5: DIAGNOSTIC UTILITY ────────────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  OBJECTIVE 5: DIAGNOSTIC UTILITY — WHR vs BMI vs INTEGRATED MODEL")
print("━" * 78)

# Expand subgroup data to pseudo-individual for AUC
def expand_pseudo(df, outcome, n_col="N", predictor_cols=[]):
    y_all = []; x_all = {c: [] for c in predictor_cols}
    d = df.dropna(subset=predictor_cols + [outcome, n_col]).copy()
    d = d[d[n_col] >= 10]
    for _, row in d.iterrows():
        n_tot  = max(1, int(row[n_col]))
        n_pos  = max(0, min(int(round(row[outcome]/100 * n_tot)), n_tot))
        n_neg  = n_tot - n_pos
        y_all.extend([1]*n_pos + [0]*n_neg)
        for c in predictor_cols:
            x_all[c].extend([float(row[c])]*n_tot)
    return np.array(y_all), {c: np.array(v) for c, v in x_all.items()}

MDF_HTN["p_NormBMI_HighWHR"] = MDF_HTN["pct_NormalBMI"] * MDF_HTN["pct_WHR_High"] / 10000

y, xd = expand_pseudo(MDF_HTN, "pct_HTN",
                       predictor_cols=["p_WHR","p_BMI","p_NormBMI_HighWHR"])

sc = StandardScaler()

def auc_model(y, x_cols, xd, label):
    X = np.column_stack([xd[c] for c in x_cols])
    X_sc = sc.fit_transform(X)
    clf  = LogisticRegression(max_iter=2000, random_state=42)
    clf.fit(X_sc, y)
    prob = clf.predict_proba(X_sc)[:,1]
    auc  = roc_auc_score(y, prob)
    fpr, tpr, thr = roc_curve(y, prob)
    # Youden index
    j    = tpr - fpr
    best = np.argmax(j)
    sens = tpr[best]; spec = 1 - fpr[best]
    # Likelihood ratios
    lr_pos = sens / (1 - spec) if (1-spec) > 0 else np.inf
    lr_neg = (1 - sens) / spec if spec > 0 else 0
    # Positive predictive accuracy proxy
    return auc, fpr, tpr, sens, spec, lr_pos, lr_neg, thr[best]

auc_whr,  fpr_w, tpr_w, se_w, sp_w, lrp_w, lrn_w, thr_w  = auc_model(
    y, ["p_WHR"], xd, "WHR alone")
auc_bmi,  fpr_b, tpr_b, se_b, sp_b, lrp_b, lrn_b, thr_b  = auc_model(
    y, ["p_BMI"], xd, "BMI alone")
auc_both, fpr_c, tpr_c, se_c, sp_c, lrp_c, lrn_c, thr_c  = auc_model(
    y, ["p_WHR","p_BMI"], xd, "WHR+BMI")
auc_full, fpr_f, tpr_f, se_f, sp_f, lrp_f, lrn_f, thr_f  = auc_model(
    y, ["p_WHR","p_BMI","p_NormBMI_HighWHR"], xd, "Full+MUNW")

print(f"\n  {'Model':<30} {'AUC':>7} {'Sens%':>7} {'Spec%':>7} {'LR+':>7} {'LR-':>7}")
print("  " + "─"*65)
for name, a, se, sp, lrp, lrn in [
    ("WHR ≥ 0.85 alone",    auc_whr,  se_w, sp_w, lrp_w, lrn_w),
    ("BMI ≥ 25 alone",      auc_bmi,  se_b, sp_b, lrp_b, lrn_b),
    ("WHR + BMI",           auc_both, se_c, sp_c, lrp_c, lrn_c),
    ("WHR + BMI + MUNW",    auc_full, se_f, sp_f, lrp_f, lrn_f),
]:
    print(f"  {name:<30} {a:>7.4f} {se*100:>6.1f}% {sp*100:>6.1f}% "
          f"{lrp:>7.3f} {lrn:>7.4f}")

print(f"\n  ΔAUC (adding WHR to BMI):       {auc_both-auc_bmi:+.4f}")
print(f"  ΔAUC (adding MUNW term):        {auc_full-auc_both:+.4f}")

# Number Needed to Screen (NNS)
prev   = MDF_HTN["pct_HTN"].mean()/100
ppv_w  = se_w*prev / (se_w*prev + (1-sp_w)*(1-prev))
ppv_b  = se_b*prev / (se_b*prev + (1-sp_b)*(1-prev))
ppv_c  = se_c*prev / (se_c*prev + (1-sp_c)*(1-prev))
nns_w  = 1/ppv_w if ppv_w>0 else np.inf
nns_b  = 1/ppv_b if ppv_b>0 else np.inf
nns_c  = 1/ppv_c if ppv_c>0 else np.inf
print(f"\n  Estimated Population HTN Prevalence: {prev*100:.1f}%")
print(f"  PPV (WHR alone):  {ppv_w*100:.1f}%  → NNS = {nns_w:.1f}")
print(f"  PPV (BMI alone):  {ppv_b*100:.1f}%  → NNS = {nns_b:.1f}")
print(f"  PPV (WHR+BMI):    {ppv_c*100:.1f}%  → NNS = {nns_c:.1f}")

# NRI
def nri(y, prob_old, prob_new, thr=0.15):
    cat_old = (prob_old >= thr).astype(int)
    cat_new = (prob_new >= thr).astype(int)
    ev  = y == 1
    non = y == 0
    up_ev   = np.sum((cat_new>cat_old)&ev);   dn_ev  = np.sum((cat_new<cat_old)&ev)
    up_non  = np.sum((cat_new>cat_old)&non);  dn_non = np.sum((cat_new<cat_old)&non)
    nri_e  = (up_ev  - dn_ev)  / ev.sum()  if ev.sum()>0  else 0
    nri_ne = (dn_non - up_non) / non.sum() if non.sum()>0 else 0
    return nri_e, nri_ne, nri_e+nri_ne

from sklearn.linear_model import LogisticRegression
def pred_prob(y, x_cols, xd):
    X = np.column_stack([xd[c] for c in x_cols])
    sc_ = StandardScaler()
    clf = LogisticRegression(max_iter=2000, random_state=42)
    clf.fit(sc_.fit_transform(X), y)
    return clf.predict_proba(sc_.transform(X))[:,1]

prob_bmi_only = pred_prob(y, ["p_BMI"], xd)
prob_whr_bmi  = pred_prob(y, ["p_WHR","p_BMI"], xd)
nri_e, nri_ne, nri_tot = nri(y, prob_bmi_only, prob_whr_bmi)
print(f"\n  NRI (adding WHR to BMI-only model):")
print(f"    Events NRI    : {nri_e*100:+.1f}%")
print(f"    Non-Events NRI: {nri_ne*100:+.1f}%")
print(f"    ★ Total NRI   : {nri_tot*100:+.1f}%")

# Integrated Discrimination Improvement (IDI)
idi = (prob_whr_bmi[y==1].mean() - prob_whr_bmi[y==0].mean()) - \
      (prob_bmi_only[y==1].mean() - prob_bmi_only[y==0].mean())
print(f"\n  Integrated Discrimination Improvement (IDI): {idi:.6f}")
print(f"  ({'WHR adds discriminative information' if idi>0 else 'no improvement'})")


# ════════════════════════════════════════════════════════════════════════════
# ── OBJ 6: DIAGNOSTIC GAPS — RURAL & TRIBAL FOCUS ───────────────────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  OBJECTIVE 6: DIAGNOSTIC GAPS — WAIST-CENTRIC vs WEIGHT-CENTRIC CARE")
print("  Focus: Rural, Tribal, and Low-Education Subgroups")
print("━" * 78)

# Gap analysis: women missed by BMI but captured by WHR
# = MUNW women = Group B = normal BMI but high WHR

gap_results = []
for var, label in [
    ("Residence",  "Residence"),
    ("Caste/tribe","Caste/Tribe"),
    ("Schooling",  "Education"),
]:
    sub_htn = MDF_HTN[MDF_HTN["Variable"]==var].copy()
    sub_htn["n_MUNW_missed"] = (
        sub_htn["pct_NormalBMI"] * sub_htn["pct_WHR_High"] / 10000 * sub_htn["N"]
    ).round()
    sub_htn["pct_MUNW"] = sub_htn["pct_NormalBMI"] * sub_htn["pct_WHR_High"] / 100
    for sv in sorted(sub_htn["SubVariable"].unique()):
        d = sub_htn[sub_htn["SubVariable"]==sv]
        if len(d)==0: continue
        gap_results.append({
            "Variable": label,
            "SubGroup": sv,
            "WHR_High_mean": d["pct_WHR_High"].mean(),
            "HTN_mean":      d["pct_HTN"].mean(),
            "OW_Obese_mean": d["pct_OW_Obese"].mean(),
            "MUNW_pct":      d["pct_MUNW"].mean(),
            "N_total":       d["N"].sum(),
            "N_MUNW_est":    d["n_MUNW_missed"].sum(),
        })

gap_df = pd.DataFrame(gap_results)
print(f"\n  {'Variable':<14} {'SubGroup':<30} {'WHR%':>7} {'OW%':>7} {'MUNW%':>8} {'HTN%':>7} {'N_MUNW':>10}")
print("  " + "─"*88)
for _, r in gap_df.iterrows():
    print(f"  {r['Variable']:<14} {r['SubGroup']:<30} {r['WHR_High_mean']:>6.1f}% "
          f"{r['OW_Obese_mean']:>6.1f}% {r['MUNW_pct']:>7.1f}% "
          f"{r['HTN_mean']:>6.1f}% {r['N_MUNW_est']:>10,.0f}")

# Key comparisons: Rural vs Urban MUNW
rural_munw = gap_df[(gap_df["Variable"]=="Residence")&(gap_df["SubGroup"]=="Rural")]["MUNW_pct"].values
urban_munw = gap_df[(gap_df["Variable"]=="Residence")&(gap_df["SubGroup"]=="Urban")]["MUNW_pct"].values
if len(rural_munw) and len(urban_munw):
    print(f"\n  Rural  MUNW mean: {rural_munw[0]:.2f}%")
    print(f"  Urban  MUNW mean: {urban_munw[0]:.2f}%")

# Tribal vs non-tribal
tribe = gap_df[(gap_df["Variable"]=="Caste/Tribe")&(gap_df["SubGroup"]=="Scheduled tribe")]
other = gap_df[(gap_df["Variable"]=="Caste/Tribe")&(gap_df["SubGroup"]!="Scheduled tribe")]
if len(tribe):
    print(f"\n  Scheduled Tribe MUNW: {tribe['MUNW_pct'].values[0]:.1f}%  "
          f"HTN: {tribe['HTN_mean'].values[0]:.1f}%  "
          f"WHR High: {tribe['WHR_High_mean'].values[0]:.1f}%")
    print(f"  Other castes MUNW:    {other['MUNW_pct'].mean():.1f}%  "
          f"HTN: {other['HTN_mean'].mean():.1f}%  "
          f"WHR High: {other['WHR_High_mean'].mean():.1f}%")

# WHR-specific OR in each diagnostic gap group (MUNW within each subgroup)
print("\n  ── MUNW → HTN OR by Diagnostic Gap Subgroup ──")
diag_gap_ors = []
for var in ["Residence","Caste/tribe","Schooling"]:
    pheno_sub = PHENO_HTN[PHENO_HTN["Variable"]==var].copy()
    for sv in sorted(pheno_sub["SubVariable"].unique()):
        sub = pheno_sub[
            (pheno_sub["SubVariable"]==sv) &
            (pheno_sub["Group"].isin(["A","B"]))].copy()
        sub["is_MUNW"] = (sub["Group"]=="B").astype(float)
        sub = sub[(sub["n_out"]>0)&(sub["n_no"]>0)]
        if len(sub) < 5: continue
        try:
            Y = sub[["n_out","n_no"]].values
            X = sm.add_constant(sub[["is_MUNW"]])
            m = sm.GLM(Y, X, family=sm.families.Binomial()).fit(maxiter=200)
            OR = np.exp(m.params["is_MUNW"])
            ci = np.exp(m.conf_int().loc["is_MUNW"])
            p  = m.pvalues["is_MUNW"]
            diag_gap_ors.append({
                "Variable": var, "SubGroup": sv,
                "OR": round(OR,3), "CI_Lo": round(ci[0],3),
                "CI_Hi": round(ci[1],3), "p": p,
                "Sig": "***" if p<0.001 else "**" if p<0.01 else "*" if p<0.05 else "ns"
            })
        except: pass

dg_df = pd.DataFrame(diag_gap_ors)
print(f"\n  {'Variable':<14} {'SubGroup':<30} {'OR':>6} {'95% CI':>18} {'Sig'}")
print("  " + "─"*75)
for _, r in dg_df.iterrows():
    print(f"  {r['Variable']:<14} {r['SubGroup']:<30} {r['OR']:>6.3f}  "
          f"({r['CI_Lo']:.3f}–{r['CI_Hi']:.3f})   {r['Sig']}")

# Education gradient: does higher education attenuate the MUNW risk?
print("\n  ── Education Gradient in MUNW Risk ──")
ed_order = ["No schooling","<5 years complete","5-7 years complete",
            "8-9 years complete","10-11 years complete","12 or more years complete",
            "5-9 years complete"]
ed_ors = dg_df[dg_df["Variable"]=="Schooling"].copy()
print("  Lower education → Higher MUNW-HTN OR? (Evidence for diagnostic gap)")
for _, r in ed_ors.iterrows():
    print(f"    {r['SubGroup']:<35}: OR={r['OR']:.3f} {r['Sig']}")


# ════════════════════════════════════════════════════════════════════════════
# ── COMPREHENSIVE FIGURES ────────────────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  GENERATING PUBLICATION-QUALITY FIGURES")
print("━" * 78)

# ── FIG 1: Primary Aim — Risk gradient box plots ─────────────────────────────
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
fig.suptitle("Primary Aim: Cardiometabolic Risk Across Adiposity Phenotypes\n"
             "NFHS-5, Indian Women 15–49 Years (N ≈ 2.75 million reconstructed)",
             fontsize=12, fontweight="bold", y=1.03)
for ax, (pheno, title) in zip(axes, [
    (PHENO_HTN, "Hypertension Prevalence (%)"),
    (PHENO_BG,  "BG Dysregulation >140 mg/dl (%)"),
    (PHENO_BGV, "Very High BG >160 mg/dl (%)")
]):
    data  = [pheno[pheno["Group"]==g]["p_out"].values*100 for g in "ABCD"]
    means = [d.mean() for d in data]
    cols  = [C["A"],C["B"],C["C"],C["D"]]
    bp    = ax.boxplot(data, patch_artist=True, widths=0.55,
                        medianprops=dict(color="black",linewidth=2))
    for patch, col in zip(bp["boxes"], cols):
        patch.set_facecolor(col); patch.set_alpha(0.82)
    ax.set_xticks([1,2,3,4])
    ax.set_xticklabels(["A\nNorm BMI\nNorm WHR","B★\nMUNW\n(Norm BMI\nHigh WHR)",
                         "C\nMHO\n(High BMI\nNorm WHR)","D\nDouble\nRisk"], fontsize=8)
    ax.set_ylabel(title, fontsize=9)
    ax.set_title(title, fontsize=10, fontweight="bold")
    ax.grid(axis="y", linestyle=":", alpha=0.4)
    for i, (m, col) in enumerate(zip(means, cols)):
        ax.text(i+1, max(data[i])+0.4, f"{m:.1f}%",
                ha="center", fontsize=8, fontweight="bold", color=col)
patches = [mpatches.Patch(color=c, label=l) for l,c in zip(
    ["A: Normal Risk","B: MUNW ★","C: MHO","D: Double Risk"],
    [C["A"],C["B"],C["C"],C["D"]])]
fig.legend(handles=patches, loc="lower center", ncol=4, fontsize=8,
           bbox_to_anchor=(0.5,-0.06))
plt.tight_layout()
fig.savefig(OUT_DIR/"F01_primary_aim_phenotypes.png", bbox_inches="tight", dpi=150)
plt.close()
print("  ✔ F01_primary_aim_phenotypes.png")

# ── FIG 2: OBJ 1 — 4-group OR forest plot ────────────────────────────────────
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
fig.suptitle("OBJ 1 — Precision: ORs for Cardiometabolic Outcomes by Adiposity Phenotype\n"
             "(Reference: Group A — Normal BMI + Normal WHR)", fontweight="bold")
for ax, (m_, title) in zip(axes, [
    (m_obj1_htn, "Hypertension"),
    (m_obj1_bg,  "BG Dysregulation"),
    (m_obj1_bgv, "Very High BG"),
]):
    groups = ["B (MUNW ★)","C (MHO)","D (Double)"]
    ors_  = [np.exp(m_.params[f"grp_{g}"]) for g in "BCD"]
    cis   = [np.exp(m_.conf_int().loc[f"grp_{g}"]) for g in "BCD"]
    cols_ = [C["B"],C["C"],C["D"]]
    for i, (OR, ci, col, lbl) in enumerate(zip(ors_, cis, cols_, groups)):
        ax.barh(i, OR, color=col, alpha=0.8, height=0.55)
        ax.errorbar(OR, i, xerr=[[OR-ci[0]],[ci[1]-OR]],
                    fmt="none", color="black", capsize=5, linewidth=1.5)
        ax.text(ci[1]+0.05, i, f"{OR:.2f}", va="center", fontsize=9, fontweight="bold")
    ax.axvline(1, color="black", linestyle="--", linewidth=1)
    ax.set_yticks(range(3)); ax.set_yticklabels(groups, fontsize=9)
    ax.set_xlabel("Odds Ratio"); ax.set_title(title, fontweight="bold")
    ax.grid(axis="x", linestyle=":", alpha=0.4)
plt.tight_layout()
fig.savefig(OUT_DIR/"F02_obj1_precision_ORs.png", bbox_inches="tight", dpi=150)
plt.close()
print("  ✔ F02_obj1_precision_ORs.png")

# ── FIG 3: OBJ 2 — MUNW 2×2 risk matrix ─────────────────────────────────────
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
fig.suptitle("OBJ 2 — MUNW Phenotype: Risk Matrix (BMI × WHR)\n"
             "Red dashed box = MUNW 'Hidden Risk' zone", fontweight="bold")
for ax, (pheno, title) in zip(axes, [(PHENO_HTN,"Hypertension %"),(PHENO_BG,"BG Dysreg %")]):
    matrix = np.zeros((2,2))
    for i, bgrp in enumerate(["Normal BMI","High BMI"]):
        for j, wgrp in enumerate(["Normal WHR","High WHR"]):
            g = {("Normal BMI","Normal WHR"):"A",("Normal BMI","High WHR"):"B",
                 ("High BMI","Normal WHR"):"C",("High BMI","High WHR"):"D"}[(bgrp,wgrp)]
            matrix[i,j] = pheno[pheno["Group"]==g]["p_out"].mean()*100
    im = ax.imshow(matrix, cmap="RdYlGn_r", vmin=0, vmax=matrix.max()*1.1)
    ax.set_xticks([0,1]); ax.set_yticks([0,1])
    ax.set_xticklabels(["Normal WHR\n(<0.85)","High WHR\n(≥0.85)"], fontsize=10)
    ax.set_yticklabels(["Normal BMI\n(18.5–24.9)","High BMI\n(≥25)"], fontsize=10)
    ax.set_xlabel("WHR Status",fontsize=10); ax.set_ylabel("BMI Status",fontsize=10)
    ax.set_title(title, fontweight="bold")
    for i_ in range(2):
        for j_ in range(2):
            g = {(0,0):"A",(0,1):"B★MUNW",(1,0):"C",(1,1):"D"}[(i_,j_)]
            ax.text(j_,i_, f"Grp {g}\n{matrix[i_,j_]:.1f}%",
                    ha="center", va="center", fontsize=11, fontweight="bold",
                    color="white" if matrix[i_,j_]>matrix.max()*0.5 else "black")
    plt.colorbar(im, ax=ax, shrink=0.8)
    rect = plt.Rectangle((-0.5,-0.5),1,1, fill=False,
                          edgecolor="#E74C3C", linewidth=4, linestyle="--")
    ax.add_patch(rect)
plt.tight_layout()
fig.savefig(OUT_DIR/"F03_obj2_MUNW_heatmap.png", bbox_inches="tight", dpi=150)
plt.close()
print("  ✔ F03_obj2_MUNW_heatmap.png")

# ── FIG 4: OBJ 3 — Suppression Effect Visualisation ─────────────────────────
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
fig.suptitle("OBJ 3 — BMI Suppression/Attenuation Effect on WHR's True Risk Signal",
             fontweight="bold")
# Coefficient comparison
ax = axes[0]
models_lbl = ["WHR alone\n(Unadjusted)","WHR\n(BMI-Adjusted)"]
betas = [beta_unadj, beta_adj]
ors_  = [OR_unadj,   OR_adj]
bars  = ax.bar(models_lbl, ors_, color=[C["WHR"],"#E74C3C"], alpha=0.8,
               edgecolor="white", width=0.5)
ax.axhline(1, color="black", linestyle="--", linewidth=1)
ax.set_ylabel("Odds Ratio for WHR ≥ 0.85 (HTN)", fontsize=10)
ax.set_title("WHR OR Before vs After BMI Adjustment", fontweight="bold")
for bar, OR in zip(bars, ors_):
    ax.text(bar.get_x()+bar.get_width()/2, OR+0.02, f"OR={OR:.3f}",
            ha="center", fontsize=11, fontweight="bold")
ax.annotate(f"Attenuation\n= {attenuation_pct:.1f}%",
            xy=(0.5, (OR_unadj+OR_adj)/2),
            xytext=(0.5, (OR_unadj+OR_adj)/2),
            ha="center", va="center", fontsize=11,
            bbox=dict(boxstyle="round,pad=0.3", facecolor="#FFF3CD", edgecolor="#F39C12"))
ax.grid(axis="y", linestyle=":", alpha=0.4)
# Scatter: WHR vs BMI proportion across states
ax2 = axes[1]
ax2.scatter(state_summary["WHR_High"], state_summary["OW_Obese"],
            s=state_summary["N"]/state_summary["N"].max()*400+30,
            c=state_summary["HTN"], cmap="RdYlGn_r",
            alpha=0.8, edgecolors="grey", linewidths=0.4)
r_whr_bmi, p_whr_bmi = stats.pearsonr(state_summary["WHR_High"],
                                        state_summary["OW_Obese"])
ax2.set_xlabel("Central Obesity — WHR ≥ 0.85 (%)", fontsize=10)
ax2.set_ylabel("General Obesity — BMI ≥ 25 (%)", fontsize=10)
ax2.set_title(f"WHR–BMI Overlap Across States\nr={r_whr_bmi:.3f} p={p_whr_bmi:.3f}"
              f"\n(Negative corr → WHR & BMI capture DIFFERENT women)", fontweight="bold")
for _, r in state_summary.iterrows():
    ax2.annotate(r["State"].replace("_"," ")[:8],
                 (r["WHR_High"],r["OW_Obese"]),
                 textcoords="offset points", xytext=(3,3), fontsize=6, color="grey")
ax2.grid(linestyle=":", alpha=0.4)
plt.tight_layout()
fig.savefig(OUT_DIR/"F04_obj3_suppression.png", bbox_inches="tight", dpi=150)
plt.close()
print("  ✔ F04_obj3_suppression.png")

# ── FIG 5: OBJ 4 — Regional Disparities ─────────────────────────────────────
fig, axes = plt.subplots(1, 2, figsize=(16, 7))
fig.suptitle("OBJ 4 — Regional Disparities: Central Obesity & HTN Across Indian States",
             fontweight="bold")
ss = state_summary.sort_values("WHR_High", ascending=True)
reg_colors = {"North":"#3498DB","East":"#2ECC71","Northeast":"#E74C3C",
              "West":"#F39C12","Central":"#9B59B6","South":"#1ABC9C","Other":"#95A5A6"}
colors_bar = [reg_colors.get(ss.loc[i,"Region"],"#95A5A6") for i in ss.index]
axes[0].barh(ss["State"].str.replace("_"," "), ss["WHR_High"],
             color=colors_bar, edgecolor="white", alpha=0.85)
axes[0].axvline(ss["WHR_High"].mean(), color="black", linestyle="--",
                linewidth=1.5, label=f"Mean={ss['WHR_High'].mean():.1f}%")
axes[0].set_xlabel("Central Obesity — WHR ≥ 0.85 (%)", fontsize=10)
axes[0].set_title("WHR ≥ 0.85 Prevalence by State\n(Colour = Region)", fontweight="bold")
axes[0].legend(fontsize=9); axes[0].grid(axis="x", linestyle=":", alpha=0.4)
patches_reg = [mpatches.Patch(color=c, label=r) for r, c in reg_colors.items()]
axes[0].legend(handles=patches_reg, fontsize=7, loc="lower right")

sc2 = axes[1].scatter(state_summary["WHR_High"], state_summary["HTN"],
                       s=state_summary["pct_MUNW"]*8+20,
                       c=[reg_colors.get(r,"grey") for r in state_summary["Region"]],
                       alpha=0.85, edgecolors="white", linewidths=0.8)
for _, r in state_summary.iterrows():
    axes[1].annotate(r["State"].replace("_"," "),
                     (r["WHR_High"],r["HTN"]),
                     textcoords="offset points", xytext=(4,3), fontsize=7)
r_val, p_val = stats.pearsonr(state_summary["WHR_High"], state_summary["HTN"])
axes[1].set_xlabel("Central Obesity — WHR ≥ 0.85 (%)", fontsize=10)
axes[1].set_ylabel("Hypertension Prevalence (%)", fontsize=10)
axes[1].set_title(f"WHR vs HTN by State (Bubble = MUNW %)\nr={r_val:.3f}, p={p_val:.4f}",
                   fontweight="bold")
axes[1].grid(linestyle=":", alpha=0.4)
plt.tight_layout()
fig.savefig(OUT_DIR/"F05_obj4_regional.png", bbox_inches="tight", dpi=150)
plt.close()
print("  ✔ F05_obj4_regional.png")

# ── FIG 6: OBJ 5 — ROC curves + diagnostic utility ───────────────────────────
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
fig.suptitle("OBJ 5 — Diagnostic Utility: WHR vs BMI vs Integrated Model", fontweight="bold")
ax = axes[0]
for lbl, fpr_, tpr_, auc_, col in [
    ("WHR alone",     fpr_w, tpr_w, auc_whr,  C["WHR"]),
    ("BMI alone",     fpr_b, tpr_b, auc_bmi,  C["BMI"]),
    ("WHR + BMI",     fpr_c, tpr_c, auc_both, C["COMB"]),
    ("WHR+BMI+MUNW",  fpr_f, tpr_f, auc_full, "#8E44AD"),
]:
    ax.plot(fpr_, tpr_, label=f"{lbl} (AUC={auc_:.3f})", linewidth=2.5, color=col)
ax.plot([0,1],[0,1],"k--",linewidth=1,label="Random")
ax.set_xlabel("1−Specificity (FPR)", fontsize=10)
ax.set_ylabel("Sensitivity (TPR)", fontsize=10)
ax.set_title("ROC Curves — HTN Prediction", fontweight="bold")
ax.legend(fontsize=9, loc="lower right")
ax.grid(linestyle=":", alpha=0.4)

ax2 = axes[1]
metrics = ["AUC","Sensitivity","Specificity","LR+","PPV","NNS"]
whr_vals = [auc_whr, se_w*100, sp_w*100, lrp_w, ppv_w*100, nns_w]
bmi_vals = [auc_bmi, se_b*100, sp_b*100, lrp_b, ppv_b*100, nns_b]
comb_vals= [auc_both, se_c*100, sp_c*100, lrp_c, ppv_c*100, nns_c]
x = np.arange(len(metrics))
w_ = 0.25
ax2.bar(x-w_, whr_vals, w_, label="WHR alone", color=C["WHR"], alpha=0.8)
ax2.bar(x,    bmi_vals, w_, label="BMI alone", color=C["BMI"], alpha=0.8)
ax2.bar(x+w_, comb_vals,w_, label="WHR+BMI",   color=C["COMB"], alpha=0.8)
ax2.set_xticks(x); ax2.set_xticklabels(metrics, fontsize=9)
ax2.set_title("Diagnostic Performance Metrics", fontweight="bold")
ax2.legend(fontsize=9); ax2.grid(axis="y", linestyle=":", alpha=0.4)
plt.tight_layout()
fig.savefig(OUT_DIR/"F06_obj5_diagnostic.png", bbox_inches="tight", dpi=150)
plt.close()
print("  ✔ F06_obj5_diagnostic.png")

# ── FIG 7: OBJ 6 — Diagnostic Gaps (Rural, Tribal, Education) ────────────────
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
fig.suptitle("OBJ 6 — Diagnostic Gaps: MUNW Burden and WHR-HTN OR\nby Sociodemographic Subgroup",
             fontweight="bold")
# Left: MUNW % by subgroup
ax = axes[0]
gap_plot = gap_df.sort_values("MUNW_pct", ascending=True)
bar_cols  = []
for _, r in gap_plot.iterrows():
    if "Rural" in r["SubGroup"] or "Scheduled tribe" in r["SubGroup"] or "No schooling" in r["SubGroup"]:
        bar_cols.append("#E74C3C")
    else:
        bar_cols.append("#3498DB")
ax.barh([f"{r['Variable']}: {r['SubGroup']}" for _,r in gap_plot.iterrows()],
        gap_plot["MUNW_pct"], color=bar_cols, alpha=0.82, edgecolor="white")
ax.set_xlabel("MUNW Prevalence (%) — Normal BMI + High WHR", fontsize=9)
ax.set_title("MUNW Burden by Subgroup\n(Red = Priority Gap Groups)", fontweight="bold")
ax.axvline(gap_df["MUNW_pct"].mean(), color="black", linestyle="--", linewidth=1)
ax.grid(axis="x", linestyle=":", alpha=0.4)

# Right: OR for MUNW→HTN by subgroup
ax2 = axes[1]
dg_sorted = dg_df.sort_values("OR", ascending=True)
bar_cols2 = ["#E74C3C" if r["Sig"]!="ns" else "#BDC3C7" for _,r in dg_sorted.iterrows()]
ax2.barh([f"{r['Variable']}: {r['SubGroup']}" for _,r in dg_sorted.iterrows()],
         dg_sorted["OR"], color=bar_cols2, alpha=0.82, edgecolor="white")
for i, (_, r) in enumerate(dg_sorted.iterrows()):
    ax2.errorbar(r["OR"], i, xerr=[[r["OR"]-r["CI_Lo"]],[r["CI_Hi"]-r["OR"]]],
                 fmt="none", color="black", capsize=3, linewidth=1.2)
    ax2.text(r["CI_Hi"]+0.005, i, f"{r['OR']:.2f}{r['Sig']}",
             va="center", fontsize=7)
ax2.axvline(1, color="black", linestyle="--", linewidth=1)
ax2.set_xlabel("OR for MUNW → Hypertension", fontsize=9)
ax2.set_title("MUNW→HTN OR by Subgroup\n(Diagnostic Gap Evidence)", fontweight="bold")
ax2.grid(axis="x", linestyle=":", alpha=0.4)
plt.tight_layout()
fig.savefig(OUT_DIR/"F07_obj6_diagnostic_gaps.png", bbox_inches="tight", dpi=150)
plt.close()
print("  ✔ F07_obj6_diagnostic_gaps.png")

# ── FIG 8: Comprehensive Summary Dashboard ────────────────────────────────────
fig = plt.figure(figsize=(18, 10))
gs8 = gridspec.GridSpec(2, 4, figure=fig, hspace=0.55, wspace=0.45)
fig.suptitle("COMPREHENSIVE SUMMARY DASHBOARD\nWHR ≥ 0.85 & Cardiometabolic Risk — NFHS-5, Indian Women 15–49 yrs",
             fontsize=13, fontweight="bold", y=1.01)

# Panel 1: Group HTN risk
ax1 = fig.add_subplot(gs8[0,0])
grp_htn_means = [PHENO_HTN[PHENO_HTN["Group"]==g]["p_out"].mean()*100 for g in "ABCD"]
bars1 = ax1.bar(["A","B★","C","D"], grp_htn_means,
                color=[C["A"],C["B"],C["C"],C["D"]], alpha=0.85, edgecolor="white")
ax1.set_title("HTN Risk\nby Phenotype", fontsize=9, fontweight="bold")
ax1.set_ylabel("Mean HTN %"); ax1.grid(axis="y",linestyle=":",alpha=0.4)
for bar, v in zip(bars1, grp_htn_means):
    ax1.text(bar.get_x()+bar.get_width()/2, v+0.3, f"{v:.1f}%",
             ha="center", fontsize=8, fontweight="bold")

# Panel 2: Suppression effect
ax2 = fig.add_subplot(gs8[0,1])
ax2.bar(["Unadjusted","BMI-Adj"], [OR_unadj, OR_adj],
        color=[C["WHR"],"#C0392B"], alpha=0.85, edgecolor="white", width=0.5)
ax2.axhline(1, color="black", linestyle="--", linewidth=1)
ax2.set_title(f"BMI Suppression\nEffect on WHR OR\n(-{attenuation_pct:.0f}% attenuation)",
              fontsize=9, fontweight="bold")
ax2.set_ylabel("OR for WHR → HTN"); ax2.grid(axis="y",linestyle=":",alpha=0.4)
for i, v in enumerate([OR_unadj, OR_adj]):
    ax2.text(i, v+0.02, f"{v:.3f}", ha="center", fontsize=9, fontweight="bold")

# Panel 3: AUC comparison
ax3 = fig.add_subplot(gs8[0,2])
auc_labs = ["WHR\nalone","BMI\nalone","WHR\n+BMI","WHR+BMI\n+MUNW"]
auc_vals2 = [auc_whr, auc_bmi, auc_both, auc_full]
ax3.bar(auc_labs, auc_vals2, color=[C["WHR"],C["BMI"],C["COMB"],"#8E44AD"],
        alpha=0.85, edgecolor="white")
ax3.set_ylim(0.45, 0.65); ax3.set_title("AUC Comparison\n(HTN Prediction)",
                                          fontsize=9, fontweight="bold")
ax3.set_ylabel("AUC"); ax3.grid(axis="y",linestyle=":",alpha=0.4)
for i, v in enumerate(auc_vals2):
    ax3.text(i, v+0.002, f"{v:.3f}", ha="center", fontsize=8, fontweight="bold")

# Panel 4: NRI bar
ax4 = fig.add_subplot(gs8[0,3])
nri_labels = ["Events\n(HTN≥15%)","Non-Events\n(HTN<15%)","Total NRI"]
nri_vals   = [nri_e*100, nri_ne*100, nri_tot*100]
nri_cols   = ["#27AE60" if v>0 else "#E74C3C" for v in nri_vals]
ax4.bar(nri_labels, nri_vals, color=nri_cols, alpha=0.85, edgecolor="white")
ax4.axhline(0, color="black", linewidth=1)
ax4.set_title(f"Net Reclassification\nImprovement (NRI)\n(Adding WHR to BMI)",
              fontsize=9, fontweight="bold")
ax4.set_ylabel("NRI (%)"); ax4.grid(axis="y",linestyle=":",alpha=0.4)
for i, v in enumerate(nri_vals):
    ax4.text(i, v+0.3*np.sign(v), f"{v:+.1f}%", ha="center", fontsize=9, fontweight="bold")

# Panel 5: State MUNW bubble
ax5 = fig.add_subplot(gs8[1,0:2])
sc5 = ax5.scatter(state_summary["WHR_High"], state_summary["pct_MUNW"],
                   s=state_summary["HTN"]*15, alpha=0.75,
                   c=state_summary["HTN"], cmap="YlOrRd",
                   edgecolors="grey", linewidths=0.4)
plt.colorbar(sc5, ax=ax5, label="HTN %", shrink=0.8)
for _, r in state_summary.iterrows():
    ax5.annotate(r["State"].replace("_","")[:7],
                 (r["WHR_High"],r["pct_MUNW"]),
                 textcoords="offset points", xytext=(3,2), fontsize=6, color="#2E4057")
ax5.set_xlabel("WHR ≥ 0.85 Prevalence (%)", fontsize=9)
ax5.set_ylabel("MUNW Prevalence (%)", fontsize=9)
ax5.set_title("State-level MUNW vs WHR\n(Bubble size = HTN prevalence)", fontsize=9, fontweight="bold")
ax5.grid(linestyle=":", alpha=0.4)

# Panel 6: Diagnostic gaps OR (key subgroups)
ax6 = fig.add_subplot(gs8[1,2:])
key_subgrps = ["Residence: Rural","Residence: Urban",
               "Caste/tribe: Scheduled tribe","Caste/tribe: Scheduled caste",
               "Schooling: No schooling","Schooling: 12 or more years complete"]
key_ors = []
for s in key_subgrps:
    var, sv = s.split(": ")
    match = dg_df[(dg_df["Variable"]==var)&(dg_df["SubGroup"]==sv)]
    key_ors.append(match["OR"].values[0] if len(match) else np.nan)
valid = [(l,o) for l,o in zip(key_subgrps,key_ors) if not np.isnan(o)]
labs_v, ors_v = zip(*sorted(valid, key=lambda x: x[1]))
bar_cols6 = ["#E74C3C" if "Rural" in l or "tribe" in l or "No schooling" in l
              else "#3498DB" for l in labs_v]
ax6.barh(list(labs_v), list(ors_v), color=bar_cols6, alpha=0.82, edgecolor="white")
ax6.axvline(1, color="black", linestyle="--", linewidth=1)
ax6.set_xlabel("OR: MUNW → Hypertension", fontsize=9)
ax6.set_title("Diagnostic Gap Groups\n(Red = Priority gap, WHR adds value)", fontsize=9, fontweight="bold")
ax6.grid(axis="x", linestyle=":", alpha=0.4)

plt.tight_layout()
fig.savefig(OUT_DIR/"F08_comprehensive_dashboard.png", bbox_inches="tight", dpi=150)
plt.close()
print("  ✔ F08_comprehensive_dashboard.png")

# ════════════════════════════════════════════════════════════════════════════
# ── FINAL SUMMARY TABLE ──────────────────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
print("\n" + "━" * 78)
print("  FINAL SUMMARY OF KEY RESULTS")
print("━" * 78)

print(f"""
  PRIMARY AIM
  ─────────────────────────────────────────────────────────────────
  WHR ≥ 0.85 → HTN (Unadj):          OR = {np.exp(m_unadj.params['p_WHR']):.3f}  ***
  WHR ≥ 0.85 → HTN (BMI-adj):        OR = {np.exp(m_adj.params['p_WHR']):.3f}  ***
  WHR ≥ 0.85 → BG Dysreg (Unadj):    OR = {np.exp(m_bg_u.params['p_WHR']):.3f}  ***
  WHR ≥ 0.85 → BG Dysreg (BMI-adj):  OR = {np.exp(m_bg_a.params['p_WHR']):.3f}  ***

  OBJ 1 — PRECISION (4-Group ORs vs Reference Group A)
  ─────────────────────────────────────────────────────────────────
  MUNW/NWCO (Normal BMI + High WHR) → HTN:  OR = {np.exp(m_obj1_htn.params['grp_B']):.3f}  ***
  MHO (High BMI + Normal WHR)       → HTN:  OR = {np.exp(m_obj1_htn.params['grp_C']):.3f}  ***
  Double Risk (High BMI + High WHR)  → HTN:  OR = {np.exp(m_obj1_htn.params['grp_D']):.3f}  ***

  OBJ 2 — MUNW PHENOTYPE
  ─────────────────────────────────────────────────────────────────
  Total MUNW women (estimated):    {total_munw:,.0f}
  MUNW as % of all reconstructed:  {munw_n/total_n*100:.1f}%
  MUNW mean HTN risk:              {munw['p_out'].mean()*100:.2f}%
  Reference (A) mean HTN risk:     {ref['p_out'].mean()*100:.2f}%
  MUNW excess HTN risk:            +{(munw['p_out'].mean()-ref['p_out'].mean())*100:.2f}%

  OBJ 3 — BMI SUPPRESSION EFFECT
  ─────────────────────────────────────────────────────────────────
  WHR β attenuation when BMI added (HTN): {attenuation_pct:.1f}%
  WHR β attenuation when BMI added (BG):  {atten_bg:.1f}%
  WHR independent effect (after BMI):     {100-attenuation_pct:.1f}%

  OBJ 4 — REGIONAL DISPARITIES
  ─────────────────────────────────────────────────────────────────
  Highest WHR state:  {state_summary.iloc[0]['State']} ({float(state_summary.iloc[0]['WHR_High']):.1f}%)
  Lowest WHR state:   {state_summary.iloc[-1]['State']} ({float(state_summary.iloc[-1]['WHR_High']):.1f}%)
  WHR–HTN correlation: r={r_htn:.3f}, p={p_htn:.4f}

  OBJ 5 — DIAGNOSTIC UTILITY
  ─────────────────────────────────────────────────────────────────
  AUC WHR alone:    {auc_whr:.4f}  |  Sensitivity={se_w*100:.1f}%  Specificity={sp_w*100:.1f}%
  AUC BMI alone:    {auc_bmi:.4f}  |  NNS={nns_b:.1f}
  AUC WHR+BMI:      {auc_both:.4f}  |  NNS={nns_c:.1f}
  Total NRI (adding WHR to BMI): {nri_tot*100:+.1f}%
  IDI:              {idi:.6f}

  OBJ 6 — DIAGNOSTIC GAPS
  ─────────────────────────────────────────────────────────────────
  Rural MUNW prevalence:         {rural_munw[0]:.1f}% (significant hidden risk)
  Scheduled Tribe WHR High:      {tribe['WHR_High_mean'].values[0]:.1f}%
  MUNW-HTN OR (Rural):           {dg_df[(dg_df['Variable']=='Residence')&(dg_df['SubGroup']=='Rural')]['OR'].values[0]:.3f} ***
  MUNW-HTN OR (Scheduled Tribe): {dg_df[(dg_df['Variable']=='Caste/tribe')&(dg_df['SubGroup']=='Scheduled tribe')]['OR'].values[0]:.3f} ***
""")

# Save all results to CSV
state_summary.to_csv(OUT_DIR/"state_regional_summary.csv", index=False)
gap_df.to_csv(OUT_DIR/"diagnostic_gap_subgroups.csv", index=False)
dg_df.to_csv(OUT_DIR/"MUNW_HTN_OR_subgroups.csv", index=False)

