# -*- coding: utf-8 -*-
"""
합성 KNHANES microdata 생성기
============================

실제 국민건강영양조사(KNHANES) microdata는 공개 다운로드/네트워크 접근이
제한되므로, 본 스크립트는 한국 성인 건강영양조사를 *모사*한 **합성
microdata**를 생성한다.

중요: 산출물 CSV는 실제 KNHANES 데이터가 아니다. 공개적으로 알려진
유병률 추세에 통계적으로 근접하도록 난수 생성한 합성 데이터이며,
도구 시연·방법론 검증 목적으로만 사용해야 한다.

근사 목표(공개 통계 추세):
  - 성인 비만(BMI >= 25)  유병률  약 38%
  - 성인 고도비만(BMI >= 30) 유병률 약 7%
  - 당뇨병(T2DM) 유병률      약 12~13%
  - 고혈압 유병률            약 28~30%
  - 이상지질혈증 유병률      약 38~40%
  - 현재 흡연                약 18~20%
  - 최근 GLP-1RA 사용        약 1~2% (낮은 비율)

복합표본 설계를 단순 모사하기 위해 표본가중치(weight) 컬럼을 포함한다.

사용:
    python3 generate_knhanes_synthetic.py
출력:
    knhanes_synthetic.csv  (스크립트와 같은 디렉토리)
"""

import os
import numpy as np
import pandas as pd

SEED = 20260517          # 재현성을 위한 고정 시드
N = 7000                 # 합성 표본 크기 (6000~8000 범위)
KOR_ADULT_POP = 44_000_000  # 한국 성인(19세 이상) 인구 외삽 기준값 (근사)


def generate(n=N, seed=SEED):
    """합성 KNHANES microdata DataFrame을 생성해 반환한다."""
    rng = np.random.default_rng(seed)

    # --- 인구학 ---------------------------------------------------------
    # 성별: 0 = 남성, 1 = 여성
    sex = rng.integers(0, 2, size=n)

    # 연령: 19~80세, 중년에 약간 치우친 분포
    age = np.clip(rng.normal(48, 16, size=n), 19, 80).round().astype(int)

    # --- BMI ------------------------------------------------------------
    # 비만(>=25) 약 38%, 고도비만(>=30) 약 7%에 맞춘 로그정규 근사 분포.
    # 남성이 평균적으로 약간 높게.
    base_bmi = rng.normal(0.0, 1.0, size=n)
    bmi = 23.9 + base_bmi * 3.9 + (sex == 0) * 0.9
    # 연령에 따른 완만한 상승(중년 정점)
    bmi += -0.0016 * (age - 50) ** 2 + 0.06 * (age - 50)
    bmi = np.clip(bmi, 15.0, 47.0).round(1)

    # --- 허리둘레 (cm) --------------------------------------------------
    # BMI와 강한 상관, 남성이 더 큼. 복부비만 기준: 남>=90, 여>=85
    waist = (bmi * 2.42
             + (sex == 0) * 6.0
             + 30.0
             + rng.normal(0, 4.0, size=n))
    waist = np.clip(waist, 55.0, 145.0).round(1)

    # --- 혈당 / HbA1c ---------------------------------------------------
    # 공복혈당(mg/dL), HbA1c(%) — BMI/연령과 양의 상관
    fpg = (90.0
           + 0.8 * (bmi - 23.9)
           + 0.30 * (age - 48)
           + rng.normal(0, 10.0, size=n))
    fpg = np.clip(fpg, 65.0, 320.0).round(0)

    hba1c = (5.35
             + 0.030 * (bmi - 23.9)
             + 0.011 * (age - 48)
             + (fpg - 90.0) * 0.011
             + rng.normal(0, 0.32, size=n))
    hba1c = np.clip(hba1c, 4.2, 13.5).round(1)

    # 당뇨병 치료(약물 복용) 플래그 — 일부 환자는 혈당이 조절됨
    on_dm_med = (rng.random(n) < np.clip(0.025 + 0.0075 * (age - 40), 0.012, 0.26)).astype(int)

    # T2DM 정의: 공복혈당>=126 또는 HbA1c>=6.5 또는 당뇨약 복용
    t2dm = ((fpg >= 126) | (hba1c >= 6.5) | (on_dm_med == 1)).astype(int)

    # --- 혈압 -----------------------------------------------------------
    sbp = (119.0
           + 0.60 * (bmi - 23.9)
           + 0.48 * (age - 48)
           + rng.normal(0, 13.0, size=n))
    sbp = np.clip(sbp, 85.0, 210.0).round(0)
    dbp = (76.0
           + 0.32 * (bmi - 23.9)
           + 0.12 * (age - 48)
           + rng.normal(0, 8.5, size=n))
    dbp = np.clip(dbp, 50.0, 130.0).round(0)
    on_htn_med = (rng.random(n) < np.clip(0.03 + 0.013 * (age - 40), 0.02, 0.50)).astype(int)

    # 고혈압 정의: SBP>=140 또는 DBP>=90 또는 항고혈압제 복용
    htn = ((sbp >= 140) | (dbp >= 90) | (on_htn_med == 1)).astype(int)

    # --- 지질 -----------------------------------------------------------
    tot_chol = (188.0
                + 0.7 * (bmi - 23.9)
                + 0.25 * (age - 48)
                + rng.normal(0, 33.0, size=n))
    tot_chol = np.clip(tot_chol, 100.0, 340.0).round(0)

    tg = np.exp(rng.normal(4.78, 0.45, size=n)) + 1.6 * (bmi - 23.9)
    tg = np.clip(tg, 30.0, 700.0).round(0)

    hdl = (54.0
           - 0.55 * (bmi - 23.9)
           + (sex == 1) * 8.0
           + rng.normal(0, 11.0, size=n))
    hdl = np.clip(hdl, 18.0, 110.0).round(0)

    on_lipid_med = (rng.random(n) < np.clip(0.03 + 0.009 * (age - 40), 0.02, 0.35)).astype(int)

    # 이상지질혈증 정의: 총콜레스테롤>=240 또는 TG>=200 또는
    #   HDL<40(남)/<50(여) 또는 지질강하제 복용
    low_hdl = ((sex == 0) & (hdl < 40)) | ((sex == 1) & (hdl < 50))
    dyslipidemia = ((tot_chol >= 240) | (tg >= 200) | low_hdl | (on_lipid_med == 1)).astype(int)

    # --- 간효소 (MASLD 대용지표) ---------------------------------------
    # ALT/AST(U/L) — BMI/대사이상과 양의 상관
    alt = np.exp(rng.normal(2.95, 0.42, size=n)) + 0.9 * (bmi - 23.9)
    alt = np.clip(alt, 5.0, 200.0).round(0)
    ast = np.exp(rng.normal(2.95, 0.34, size=n)) + 0.45 * (bmi - 23.9)
    ast = np.clip(ast, 8.0, 160.0).round(0)

    # MASLD 대용 플래그: 과체중/비만(BMI>=25) + ALT 상승(남>40,여>35)
    #   — 영상검사가 없는 KNHANES형 데이터에서의 보수적 대용지표
    alt_high = ((sex == 0) & (alt > 40)) | ((sex == 1) & (alt > 35))
    masld_surrogate = ((bmi >= 25) & alt_high).astype(int)

    # --- OSA 위험 (대용 점수) ------------------------------------------
    # 영상수면검사 부재 → STOP-Bang 유사 대용 점수.
    #   비만(BMI>=30) + 큰 허리둘레 + 고령 + 남성 + 고혈압 기반
    osa_score = ((bmi >= 30).astype(int)
                 + ((sex == 0) & (waist >= 100)).astype(int)
                 + ((sex == 1) & (waist >= 95)).astype(int)
                 + (age >= 50).astype(int)
                 + (sex == 0).astype(int)
                 + htn)
    # OSA 고위험: 점수 >= 3
    osa_high_risk = (osa_score >= 3).astype(int)

    # --- 흡연 -----------------------------------------------------------
    # 현재 흡연: 남성에서 더 흔함, 전체 약 18~20%
    smoke_p = np.where(sex == 0, 0.32, 0.06)
    current_smoker = (rng.random(n) < smoke_p).astype(int)

    # --- 최근 GLP-1RA 사용 (낮은 비율) ---------------------------------
    # 비만/당뇨에서 약간 더 높지만 전체적으로 드묾(약 1~2%)
    glp1_p = np.clip(0.006
                     + 0.011 * (bmi >= 30)
                     + 0.010 * (t2dm == 1),
                     0.003, 0.06)
    recent_glp1ra = (rng.random(n) < glp1_p).astype(int)

    # --- 복합표본 가중치 -----------------------------------------------
    # 단순 모사: 양의 로그정규 가중치를 생성한 뒤,
    #   합이 한국 성인 인구 외삽 기준값이 되도록 스케일.
    raw_w = np.exp(rng.normal(0.0, 0.45, size=n))
    weight = raw_w / raw_w.sum() * KOR_ADULT_POP
    weight = weight.round(1)

    df = pd.DataFrame({
        "id": np.arange(1, n + 1),
        "age": age,
        "sex": sex,                       # 0=남성, 1=여성
        "bmi": bmi,
        "waist_cm": waist,
        "fpg_mgdl": fpg,
        "hba1c_pct": hba1c,
        "sbp_mmhg": sbp,
        "dbp_mmhg": dbp,
        "tot_chol_mgdl": tot_chol,
        "tg_mgdl": tg,
        "hdl_mgdl": hdl,
        "alt_ul": alt,
        "ast_ul": ast,
        "on_dm_med": on_dm_med,
        "on_htn_med": on_htn_med,
        "on_lipid_med": on_lipid_med,
        "t2dm": t2dm,
        "htn": htn,
        "dyslipidemia": dyslipidemia,
        "masld_surrogate": masld_surrogate,
        "osa_high_risk": osa_high_risk,
        "current_smoker": current_smoker,
        "recent_glp1ra": recent_glp1ra,
        "weight": weight,
    })
    return df


def _summary(df):
    """가중 유병률 요약을 출력한다 (생성 결과 점검용)."""
    w = df["weight"]
    tot = w.sum()

    def wprev(mask):
        return 100.0 * w[mask].sum() / tot

    print("합성 KNHANES microdata 요약 (가중 기준)")
    print("-" * 48)
    print(f"  표본 크기 N            : {len(df):,}")
    print(f"  가중 인구 합           : {tot:,.0f}")
    print(f"  평균 연령 (비가중)     : {df['age'].mean():.1f}")
    print(f"  여성 비율              : {wprev(df['sex'] == 1):.1f}%")
    print(f"  비만 (BMI>=25)         : {wprev(df['bmi'] >= 25):.1f}%  (목표 ~38%)")
    print(f"  고도비만 (BMI>=30)     : {wprev(df['bmi'] >= 30):.1f}%  (목표 ~7%)")
    print(f"  T2DM                   : {wprev(df['t2dm'] == 1):.1f}%  (목표 ~12-13%)")
    print(f"  고혈압                 : {wprev(df['htn'] == 1):.1f}%  (목표 ~28-30%)")
    print(f"  이상지질혈증           : {wprev(df['dyslipidemia'] == 1):.1f}%  (목표 ~38-40%)")
    print(f"  MASLD 대용             : {wprev(df['masld_surrogate'] == 1):.1f}%")
    print(f"  OSA 고위험             : {wprev(df['osa_high_risk'] == 1):.1f}%")
    print(f"  현재 흡연              : {wprev(df['current_smoker'] == 1):.1f}%  (목표 ~18-20%)")
    print(f"  최근 GLP-1RA 사용      : {wprev(df['recent_glp1ra'] == 1):.1f}%  (목표 ~1-2%)")
    print("-" * 48)


def main():
    df = generate()
    out_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            "knhanes_synthetic.csv")
    df.to_csv(out_path, index=False, encoding="utf-8")
    _summary(df)
    print(f"\n[OK] 합성 CSV 저장: {out_path}")
    print("주의: 실제 KNHANES 데이터가 아님 — 공개 유병률 추세 기반 합성 데이터.")


if __name__ == "__main__":
    main()
