#!/usr/bin/env python3
"""exdiv_judge.py — "当日是否除权" 判据（单点，供增量写路径与测试共用）。

判据（B-only，2026-09-13 采纳）：
    除权  ⇔  | (raw_close / raw_prev - 1) * 100 - pct_chg | ≥ TOL(默认 0.05 pp)
    raw_prev = **该股自身上一有效交易日的 raw_close**（文件末行，不是全市场日历前一日：
               停牌/退市/次新股各股前一交易日不同，取日历前一日会让停牌复牌股假除权）。
理由：
  - 除权日 tushare 的 pct_chg 用除权参考价口径算，与前一日 raw_close 直接相除必不吻合；
    非除权日两者吻合（残差 <0.05pp）。
  - **不要用 A=(raw_close/pre_close-1)*100 与 pct_chg 比对**：`pre_close` 是脏列（全库 4.9%、
    34.3 万行既不等于前日 raw_close 也不与 pct_chg 自洽，推算残留），用它做判据自相矛盾；
    且大比例分红票两侧同时失配（例 000338.SZ 20241018 raw 13.46/prev 13.56/pre_close 12.43/
    pct 2.09 → A=8.29 失配、B=-0.74 失配），A∧B 会把这类真除权漏掉。
实测（tushare 因子变更为真值）：
  - B-only 命中 87.3%（抽样 80 只 / 441 真除权日）；漏判 56 条因子跳幅中位 0.02%、max 0.10%，
    无一条 ≥0.5%（材料性可忽略）。
  - A∧B 仅命中 83.2%，漏的 74 条里 16 条跳幅 ≥0.5%（最大 2.81%）。
  - B-only 在非除权日误报率 0.27%~0.63%，但判据只在②末行因子兜底路径生效（罕见），
    误报后果 = 拒写并等接口恢复，属安全侧。
用途约束：本判据只在"因子来自②末行兜底"时用于**拒写守卫**；不得用于推断因子数值。
"""

TOL = 0.05   # pp，非除权日 |B - pct_chg| 的经验上限


def is_exdiv(raw_close, raw_prev, pct_chg, tol=TOL):
    """B-only 除权判定。任一入参缺失/非正 raw_prev → 返回 False（不拦，交给既有 fail-safe）。"""
    try:
        raw_close = float(raw_close); raw_prev = float(raw_prev); pct_chg = float(pct_chg)
    except (TypeError, ValueError):
        return False
    if raw_prev <= 0 or raw_close != raw_close or pct_chg != pct_chg:
        return False
    b = (raw_close / raw_prev - 1) * 100
    return abs(b - pct_chg) >= tol


if __name__ == '__main__':
    # 自检：真除权日（大比例分红，A∧B 会漏）+ 非除权日
    cases = [('000338.SZ 20241018 真除权(大比例分红)', 13.46, 13.56, 2.09, True),
             ('000001.SZ 普通交易日', 18.17, 18.60, -2.29, False),
             ('603093.SH 20260810 真除权', 12.50, 18.05, 0.4823, True),
             ('600309.SH 20260911 真除权', 74.50, 77.15, -2.4103, True)]
    bad = 0
    for name, rc, rp, pct, exp in cases:
        got = is_exdiv(rc, rp, pct)
        ok = got == exp
        bad += (not ok)
        print(f"  {'✅' if ok else '❌'} {name}: is_exdiv={got} 期望={exp} (B={(rc/rp-1)*100:.4f} pct={pct})")
    print('自检结果:', '全部通过' if not bad else f'{bad} 条不符')