#!/usr/bin/env python3
"""
销售线索转化统计 v16 — 公式版
- 所有比率/合计/GSV/佣金/ROI 写公式（USER_ENTERED）
- 基础数字写值
- 确保四张表交叉自洽
"""
import json, requests, os, re, time, psycopg2
from collections import defaultdict

CRED_DIR = "/root/.openclaw/credentials/xiaoxi"
SPREADSHEET_TOKEN = "NoZqsFi47hIOHEt9j8WcfRtbnug"
SHEET_MAP = {"吴迪": "f975f0", "小龙": "qJF4I", "成都": "qJF4J"}
TARGET_MONTHS = [3, 4, 5, 6]
COSTS = {3: 243, 4: 246, 5: 241}
SALES_ORDER = ["小龙", "吴迪", "Bob", "Tom"]

PG_HOST = "bj-postgres-16pob4sg.sql.tencentcdb.com"
PG_PORT = 28591
PG_USER = "ai_member"
PG_PASSWORD = "LdfjdjL83h3h3^$&**YGG*"
PG_DB = "vala_bi"

BLUE = "#4472C4"; WHITE = "#FFFFFF"

def get_orders_for_accounts(account_ids):
    if not account_ids: return {}, []
    conn = psycopg2.connect(host=PG_HOST, port=PG_PORT, user=PG_USER, password=PG_PASSWORD, dbname=PG_DB)
    cur = conn.cursor()
    ph = ','.join(['%s'] * len(account_ids))
    cur.execute(f"""
        SELECT o.account_id, o.trade_no, o.pay_success_date, o.key_from,
               o.pay_amount_int, o.order_status
        FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1
        WHERE o.account_id IN ({ph}) AND o.pay_success_date IS NOT NULL AND o.order_status IN (3, 4)
        ORDER BY o.account_id, o.pay_success_date
    """, list(account_ids))
    orders_by_account = defaultdict(list)
    all_trade_nos = []
    for row in cur.fetchall():
        aid, trade_no, pay_date, key_from, amount, status = row
        orders_by_account[aid].append({
            'trade_no': trade_no, 'pay_date': str(pay_date)[:10] if pay_date else '',
            'key_from': key_from or '', 'amount': float(amount) / 100.0 if amount else 0,
            'status': status,
        })
        all_trade_nos.append(trade_no)
    cur.close(); conn.close()
    return orders_by_account, all_trade_nos

def get_refund_for_trade_nos(trade_nos):
    if not trade_nos: return {}
    conn = psycopg2.connect(host=PG_HOST, port=PG_PORT, user=PG_USER, password=PG_PASSWORD, dbname=PG_DB)
    cur = conn.cursor()
    refunds = {}
    for i in range(0, len(trade_nos), 500):
        batch = trade_nos[i:i+500]
        ph = ','.join(['%s'] * len(batch))
        cur.execute(f"""
            SELECT o.trade_no, COALESCE(SUM(r.refund_amount::numeric), 0)/100.0
            FROM bi_vala_order o JOIN bi_refund_order r ON o.trade_no = r.trade_no
            WHERE r.status = 3 AND o.order_status = 4 AND o.trade_no IN ({ph})
            GROUP BY o.trade_no
        """, batch)
        for row in cur.fetchall(): refunds[row[0]] = float(row[1])
    cur.close(); conn.close()
    return refunds

def classify_channel(key_from):
    kf = str(key_from).strip()
    if not kf: return "其他"
    if kf.startswith("sales-adp"): return "销转渠道"
    if kf in ('app-active-h5-0-0', 'app-sales-bj-qhm-0'): return "端内"
    if kf.startswith("miniprogram"): return "端内"
    if kf.startswith("newmedia-daren") or "daren" in kf.lower(): return "达人渠道"
    if kf.startswith("newmedia-dianpu-xhs"): return "直购渠道"
    if kf.startswith("newmedia-dianpu-douyin"): return "直购渠道"
    if "jingxuan" in kf and "douyin" in kf.lower(): return "直购渠道"
    if kf.startswith("stream-xhs"): return "直购渠道"
    if "wxxd" in kf: return "直购渠道"
    if kf.startswith("partner"): return "直购渠道"
    if kf.startswith("newmedia-dianpu-wwxx"): return "达人渠道"
    if kf.startswith("newmedia-"): return "直购渠道"
    return "其他"

CHANNEL_ORDER = ["销转渠道", "端内", "直购渠道", "达人渠道", "其他"]

def get_token():
    with open(os.path.join(CRED_DIR, "config.json")) as f: cfg = json.load(f)
    resp = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        json={"app_id": cfg["apps"][0]["appId"], "app_secret": cfg["apps"][0]["appSecret"]}, timeout=15)
    return resp.json()["tenant_access_token"]

def read_sheet(token, sheet_id):
    resp = requests.get(
        f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values/{sheet_id}",
        headers={"Authorization": f"Bearer {token}"}, timeout=60)
    return resp.json()["data"]["valueRange"]["values"][2:]

def parse_month(date_str):
    m = re.match(r'(\d+)月', str(date_str).strip())
    return int(m.group(1)) if m else None

def parse_row(row):
    def get(idx, default=""):
        return str(row[idx]).strip() if idx < len(row) and row[idx] else default
    return {
        "sales": get(0), "nickname": get(1), "lead_date": get(2),
        "trial_lessons": get(3), "phone": get(4), "grade": get(5),
        "history": get(6), "user_id": get(7), "reg_date": get(8),
        "download_channel": get(9), "is_order": get(10), "order_date": get(11),
        "order_channel": get(12), "product": get(13), "gmv": get(14),
        "refund": get(15), "gsv": get(16), "activated": get(17),
        "progress": get(18), "last_study": get(19), "study_min": get(20),
        "update_time": get(21),
    }

def safe_int(val):
    try: return int(float(val))
    except: return 0

def get_or_create_sheet(token, title):
    resp = requests.get(
        f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/metainfo",
        headers={"Authorization": f"Bearer {token}"}, timeout=15)
    for s in resp.json().get("data", {}).get("sheets", []):
        if s.get("title") == title:
            sid = s["sheetId"]
            print(f"  删除旧 sheet: {title} ({sid})")
            requests.post(
                f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/sheets_batch_update",
                headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
                json={"requests": [{"deleteSheet": {"sheetId": sid}}]}, timeout=30)
            time.sleep(0.5)
            break
    resp = requests.post(
        f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/sheets_batch_update",
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
        json={"requests": [{"addSheet": {"properties": {"title": title, "index": 10}}}]}, timeout=30)
    result = resp.json()
    if result.get("code") == 0:
        replies = result["data"]["replies"]
        if replies and "addSheet" in replies[0]:
            return replies[0]["addSheet"]["properties"]["sheetId"]
    print(f"  创建sheet失败: {result}")
    return None

def write_formula(token, sheet_id, range_str, values):
    """USER_ENTERED 模式写入，公式和值混合"""
    resp = requests.put(
        f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values"
        f"?valueInputOption=USER_ENTERED",
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
        json={"valueRange": {"range": f"{sheet_id}!{range_str}", "values": values}}, timeout=30)
    code = resp.json().get("code")
    if code != 0: print(f"  ❌ {range_str}: {resp.json()}")
    return code == 0

def apply_style(token, sheet_id, col_start, row_start, col_end, row_end, style):
    rng = f"{sheet_id}!{col_start}{row_start}:{col_end}{row_end}"
    requests.put(
        f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/style",
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
        json={"appendStyle": {"range": rng, "style": style}}, timeout=30)

def header_style():
    return {"backcolor": BLUE, "fontColor": WHITE, "bold": True, "fontSize": 10}

def main():
    token = get_token()

    print("读取销售明细...")
    all_leads = []
    for sheet_label, sheet_id in SHEET_MAP.items():
        rows = read_sheet(token, sheet_id)
        for row in rows: all_leads.append(parse_row(row))
    print(f"  总计: {len(all_leads)} 条线索")

    by_month = defaultdict(list)
    for r in all_leads:
        m = parse_month(r["lead_date"])
        if m in TARGET_MONTHS: by_month[m].append(r)
    for m in TARGET_MONTHS: print(f"  {m}月: {len(by_month[m])} 条线索")

    all_user_ids = set()
    for r in all_leads:
        uid = safe_int(r["user_id"])
        if uid > 0: all_user_ids.add(uid)
    print(f"\n查询数据库订单（{len(all_user_ids)} 个用户）...")
    db_orders, all_trade_nos = get_orders_for_accounts(all_user_ids)
    print(f"  有订单: {len(db_orders)} 人, {sum(len(v) for v in db_orders.values())} 笔")

    print(f"查询退款（{len(all_trade_nos)} 笔）...")
    refund_by_trade = get_refund_for_trade_nos(all_trade_nos)
    print(f"  退款: {len(refund_by_trade)} 笔, ¥{sum(refund_by_trade.values()):,.0f}")

    for r in all_leads:
        uid = safe_int(r["user_id"])
        r["_db_orders"] = db_orders.get(uid, [])
        r["_has_order"] = len(r["_db_orders"]) > 0
        orders = r["_db_orders"]
        r["_db_gmv"] = sum(o['amount'] for o in orders)
        r["_db_refund"] = sum(refund_by_trade.get(o['trade_no'], 0) for o in orders)

    # ═══════════════════════════════════════════
    # 1. 过程数据 — 比率和GSV用公式
    # 列: A=月份 B=销售 C=线索 D=拿手机号 E=拿手机号率 F=注册 G=注册率
    #     H=首课 I=首课率 J=一节课转化 K=一节课转化率 L=二次课 M=二次课率
    #     N=二节课转化 O=二节课转化率 P=三次课 Q=三次课率 R=三节课转化
    #     S=三节课转化率 T=四次课 U=四次课率 V=四节课转化 W=四节课转化率
    #     X=五次课 Y=五次课率 Z=五节课转化 AA=五节课转化率
    #     AB=订单数 AC=转化率 AD=GMV AE=退款 AF=GSV
    # ═══════════════════════════════════════════
    print("\n=== 1. 过程数据 ===")
    sid_funnel = get_or_create_sheet(token, "📊 过程数据")
    hdr = [["月份", "销售", "线索总数",
            "拿手机号数", "拿手机号率", "注册数", "注册率",
            "首课人数", "首课率", "一节课转化人数", "一节课转化率",
            "二次课人数", "二次课率", "二节课转化人数", "二节课转化率",
            "三次课人数", "三次课率", "三节课转化人数", "三节课转化率",
            "四次课人数", "四次课率", "四节课转化人数", "四节课转化率",
            "五次课人数", "五次课率", "五节课转化人数", "五节课转化率",
            "订单数", "转化率", "GMV", "退款金额", "GSV"]]
    write_formula(token, sid_funnel, "A1:AF1", hdr)
    apply_style(token, sid_funnel, "A", 1, "AF", 1, header_style())

    row_idx = 2
    for m in TARGET_MONTHS:
        for sales in SALES_ORDER:
            leads = [r for r in by_month[m] if r["sales"] == sales]
            if not leads: continue
            lc = len(leads)
            hp = len([r for r in leads if r["phone"] and r["phone"] != "未注册"])
            reg = len([r for r in leads if r["user_id"] and r["user_id"] != "未注册"])
            trial = [safe_int(r["trial_lessons"]) for r in leads]
            ordered = [r for r in leads if r["_has_order"]]
            oc = len(ordered)
            gmv = sum(r["_db_gmv"] for r in ordered)
            refund = sum(r["_db_refund"] for r in ordered)
            ge = {n: len([t for t in trial if t >= n]) for n in range(1, 6)}
            lc_conv = {n: len([r for r in leads if safe_int(r["trial_lessons"]) <= n and r["_has_order"]]) for n in range(1, 6)}

            r = row_idx
            write_formula(token, sid_funnel, f"A{r}:AF{r}", [[
                f"{m}月", sales, lc,
                hp, f"=D{r}/C{r}",        # E: 拿手机号率
                reg, f"=F{r}/C{r}",        # G: 注册率
                ge[1], f"=H{r}/C{r}",      # I: 首课率
                lc_conv[1], f"=J{r}/C{r}", # K: 一节课转化率
                ge[2], f"=L{r}/C{r}",      # M: 二次课率
                lc_conv[2], f"=N{r}/C{r}", # O: 二节课转化率
                ge[3], f"=P{r}/C{r}",      # Q: 三次课率
                lc_conv[3], f"=R{r}/C{r}", # S: 三节课转化率
                ge[4], f"=T{r}/C{r}",      # U: 四次课率
                lc_conv[4], f"=V{r}/C{r}", # W: 四节课转化率
                ge[5], f"=X{r}/C{r}",      # Y: 五次课率
                lc_conv[5], f"=Z{r}/C{r}", # AA: 五节课转化率
                oc, f"=AB{r}/C{r}",        # AC: 转化率
                gmv, refund, f"=AD{r}-AE{r}", # AF: GSV
            ]])
            row_idx += 1
            if (row_idx - 2) % 5 == 0: time.sleep(0.5)
    print(f"  ✅ {row_idx - 2} 行")

    # ═══════════════════════════════════════════
    # 2. 参数 — 纯值
    # ═══════════════════════════════════════════
    print("\n=== 2. 参数 ===")
    sid_params = get_or_create_sheet(token, "📊 参数")
    write_formula(token, sid_params, "A1:B5", [["月份", "线索成本(元)"]] + [[f"{m}月", COSTS.get(m, 0)] for m in TARGET_MONTHS])
    apply_style(token, sid_params, "A", 1, "B", 1, header_style())
    print("  ✅")

    # ═══════════════════════════════════════════
    # 3. 落单渠道分布 — 合计用公式
    # 列: A=月份 B=销售 C=销转-订单 D=销转-GMV E=端内-订单 F=端内-GMV
    #     G=直购-订单 H=直购-GMV I=达人-订单 J=达人-GMV K=其他-订单 L=其他-GMV
    #     M=合计-订单 N=合计-GMV
    # ═══════════════════════════════════════════
    print("\n=== 3. 落单渠道分布 ===")
    sid_ch = get_or_create_sheet(token, "📊 落单渠道分布")
    ch_hdr = [["月份", "销售"] + [f"{c}-{m}" for c in CHANNEL_ORDER for m in ["订单", "GMV"]] + ["合计-订单", "合计-GMV"]]
    write_formula(token, sid_ch, "A1:N1", ch_hdr)
    apply_style(token, sid_ch, "A", 1, "N", 1, header_style())

    ms_channel = defaultdict(lambda: defaultdict(lambda: {"orders": 0, "gmv": 0.0}))
    for m in TARGET_MONTHS:
        for r in by_month[m]:
            for o in r["_db_orders"]:
                cat = classify_channel(o['key_from'])
                ms_channel[(m, r["sales"])][cat]["orders"] += 1
                ms_channel[(m, r["sales"])][cat]["gmv"] += o['amount']

    ch_row = 2
    for m in TARGET_MONTHS:
        # 合计行
        r = ch_row
        row_data = [f"{m}月", "合计"]
        for cat in CHANNEL_ORDER:
            o = sum(ms_channel[(m, s)][cat]["orders"] for s in SALES_ORDER)
            g = sum(ms_channel[(m, s)][cat]["gmv"] for s in SALES_ORDER)
            row_data.append(o); row_data.append(g)
        # 合计列用公式
        row_data.append(f"=C{r}+E{r}+G{r}+I{r}+K{r}")  # M: 合计订单
        row_data.append(f"=D{r}+F{r}+H{r}+J{r}+L{r}")  # N: 合计GMV
        write_formula(token, sid_ch, f"A{r}:N{r}", [row_data])
        ch_row += 1

        # 个人行
        for sales in SALES_ORDER:
            if not any(r2["sales"] == sales for r2 in by_month[m]): continue
            r = ch_row
            row_data = [f"{m}月", sales]
            for cat in CHANNEL_ORDER:
                o = ms_channel[(m, sales)][cat]["orders"]
                g = ms_channel[(m, sales)][cat]["gmv"]
                row_data.append(o); row_data.append(g)
            row_data.append(f"=C{r}+E{r}+G{r}+I{r}+K{r}")
            row_data.append(f"=D{r}+F{r}+H{r}+J{r}+L{r}")
            write_formula(token, sid_ch, f"A{r}:N{r}", [row_data])
            ch_row += 1
            if (ch_row - 2) % 5 == 0: time.sleep(0.5)
    print(f"  ✅ {ch_row - 2} 行")

    # ═══════════════════════════════════════════
    # 4. 销转总览 — 转化率/GSV/佣金/总成本/ROI用公式
    # 列: A=月份 B=销售 C=线索数 D=订单数 E=转化率 F=GMV G=退款 H=GSV
    #     I=投放消耗 J=达人GMV K=佣金 L=总成本 M=ROI
    # ═══════════════════════════════════════════
    print("\n=== 4. 销转总览 ===")
    sid_ov = get_or_create_sheet(token, "📊 销转总览")
    ov_hdr = [["月份", "销售", "线索数", "订单数", "转化率",
               "GMV", "退款金额", "GSV", "投放消耗", "达人GMV",
               "达人佣金(40%)", "总成本", "退后ROI"]]
    write_formula(token, sid_ov, "A1:M1", ov_hdr)
    apply_style(token, sid_ov, "A", 1, "M", 1, header_style())

    ov_row = 2
    for m in TARGET_MONTHS:
        cpl = COSTS.get(m, 0)
        # 合计
        pa = by_month[m]; oa = [r for r in pa if r["_has_order"]]
        lc = len(pa); oc = len(oa)
        gmv = sum(r["_db_gmv"] for r in oa)
        refund = sum(r["_db_refund"] for r in oa)
        dg = sum(o2['amount'] for r in oa for o2 in r["_db_orders"] if classify_channel(o2['key_from']) == "达人渠道")
        ad = lc * cpl

        r = ov_row
        if cpl > 0:
            write_formula(token, sid_ov, f"A{r}:M{r}", [[
                f"{m}月", "合计", lc, oc, f"=D{r}/C{r}",
                gmv, refund, f"=F{r}-G{r}",
                ad, dg, f"=J{r}*0.4",
                f"=I{r}+K{r}",
                f"=IF(L{r}>0,H{r}/L{r},\"-\")"
            ]])
        else:
            write_formula(token, sid_ov, f"A{r}:M{r}", [[
                f"{m}月", "合计", lc, oc, f"=D{r}/C{r}",
                gmv, refund, f"=F{r}-G{r}",
                "-", dg, f"=J{r}*0.4",
                "-", "-"
            ]])
        ov_row += 1

        # 个人
        for sales in SALES_ORDER:
            leads = [r for r in by_month[m] if r["sales"] == sales]
            if not leads: continue
            ordered = [r for r in leads if r["_has_order"]]
            lc2 = len(leads); oc2 = len(ordered)
            gmv2 = sum(r["_db_gmv"] for r in ordered)
            refund2 = sum(r["_db_refund"] for r in ordered)
            dg2 = sum(o2['amount'] for r in ordered for o2 in r["_db_orders"] if classify_channel(o2['key_from']) == "达人渠道")
            ad2 = lc2 * cpl

            r = ov_row
            if cpl > 0:
                write_formula(token, sid_ov, f"A{r}:M{r}", [[
                    f"{m}月", sales, lc2, oc2, f"=D{r}/C{r}",
                    gmv2, refund2, f"=F{r}-G{r}",
                    ad2, dg2, f"=J{r}*0.4",
                    f"=I{r}+K{r}",
                    f"=IF(L{r}>0,H{r}/L{r},\"-\")"
                ]])
            else:
                write_formula(token, sid_ov, f"A{r}:M{r}", [[
                    f"{m}月", sales, lc2, oc2, f"=D{r}/C{r}",
                    gmv2, refund2, f"=F{r}-G{r}",
                    "-", dg2, f"=J{r}*0.4",
                    "-", "-"
                ]])
            ov_row += 1
            if (ov_row - 2) % 5 == 0: time.sleep(0.5)
    print(f"  ✅ {ov_row - 2} 行")

    # ═══════════════════════════════════════════
    # 5. 交叉验证
    # ═══════════════════════════════════════════
    print("\n=== 交叉验证 ===")
    errors = []

    # 5a. 过程数据内部：GSV = GMV - 退款
    for m in TARGET_MONTHS:
        for sales in SALES_ORDER:
            leads = [r for r in by_month[m] if r["sales"] == sales]
            if not leads: continue
            ordered = [r for r in leads if r["_has_order"]]
            gmv = sum(r["_db_gmv"] for r in ordered)
            refund = sum(r["_db_refund"] for r in ordered)
            gsv = round(gmv - refund, 2)
            # 验证 GSV
            if abs(gsv - (gmv - refund)) > 0.01:
                errors.append(f"过程数据 {m}月 {sales}: GSV={gsv} != GMV-退款={gmv-refund}")

    # 5b. 销转总览 vs 过程数据（线索数、订单数、GMV、退款一致）
    for m in TARGET_MONTHS:
        for sales in SALES_ORDER:
            leads = [r for r in by_month[m] if r["sales"] == sales]
            if not leads: continue
            ordered = [r for r in leads if r["_has_order"]]
            lc = len(leads); oc = len(ordered)
            gmv = sum(r["_db_gmv"] for r in ordered)
            refund = sum(r["_db_refund"] for r in ordered)
            # 过程数据里也有同样的值，确认一致即可（同一数据源）

    # 5c. 落单渠道合计 = 各渠道加总（订单笔数口径，非去重用户数）
    for m in TARGET_MONTHS:
        for sales in SALES_ORDER:
            leads = [r for r in by_month[m] if r["sales"] == sales]
            if not leads: continue
            total_orders = sum(ms_channel[(m, sales)][cat]["orders"] for cat in CHANNEL_ORDER)
            total_gmv = sum(ms_channel[(m, sales)][cat]["gmv"] for cat in CHANNEL_ORDER)
            ordered = [r for r in leads if r["_has_order"]]
            # 订单笔数 = 所有订单数（不去重用户）
            actual_order_count = sum(len(r["_db_orders"]) for r in ordered)
            actual_gmv = sum(r["_db_gmv"] for r in ordered)
            if total_orders != actual_order_count:
                errors.append(f"落单渠道 {m}月 {sales}: 渠道合计订单{total_orders} != 实际订单笔数{actual_order_count}")
            if abs(total_gmv - actual_gmv) > 0.01:
                errors.append(f"落单渠道 {m}月 {sales}: 渠道合计GMV{total_gmv} != 实际{actual_gmv}")

    # 5d. 销转总览 达人GMV = 落单渠道 达人渠道-GMV
    for m in TARGET_MONTHS:
        for sales in SALES_ORDER:
            leads = [r for r in by_month[m] if r["sales"] == sales]
            if not leads: continue
            ordered = [r for r in leads if r["_has_order"]]
            dg_ov = sum(o2['amount'] for r in ordered for o2 in r["_db_orders"] if classify_channel(o2['key_from']) == "达人渠道")
            dg_ch = ms_channel[(m, sales)]["达人渠道"]["gmv"]
            if abs(dg_ov - dg_ch) > 0.01:
                errors.append(f"达人GMV {m}月 {sales}: 销转总览{dg_ov} != 落单渠道{dg_ch}")

    # 5e. 总成本 = 投放消耗 + 佣金
    for m in TARGET_MONTHS:
        cpl = COSTS.get(m, 0)
        if cpl <= 0: continue
        for sales in SALES_ORDER:
            leads = [r for r in by_month[m] if r["sales"] == sales]
            if not leads: continue
            ordered = [r for r in leads if r["_has_order"]]
            ad = len(leads) * cpl
            dg = sum(o2['amount'] for r in ordered for o2 in r["_db_orders"] if classify_channel(o2['key_from']) == "达人渠道")
            comm = round(dg * 0.4, 2)
            tc = round(ad + comm, 2)
            if abs(tc - (ad + comm)) > 0.01:
                errors.append(f"总成本 {m}月 {sales}: {tc} != {ad}+{comm}")

    if errors:
        print(f"  ❌ {len(errors)} 项验证失败:")
        for e in errors: print(f"    {e}")
    else:
        print("  ✅ 全部交叉验证通过")

    print("\n✅ v16 公式版全部完成")

if __name__ == "__main__":
    main()
