# -*- coding: utf-8 -*-
"""个人层 H2 目标 —— 口径 A：按人年度目标占比分摊部门 H2 余额（空缺份额摊到在册人员）

口径定义（用户 2026-09-20 拍板选 A）：
  个人 H2 目标总额 = 部门 H2 额度 × (个人年度目标 / 该部门在册人员年度目标之和)
  月内节奏 = 部门 H2 月度节奏（同一部门内所有人同比例），保证
    Σ(部门内个人某月) == 部门该月目标
    Σ(部门内个人 H2) == 部门 H2 额度
    Σ(三个部门 H2) + H1 官方 == 6,950 万 == 年度目标

写入：
  数据/kpi_dashboard.json    people[].h2_months / h2_target_total   （个人页 工具/index.html 直读）
  数据/kpi_scheme.json       people[].h2_months / h2_target_total   （积分卡人员名单）
不写：部门层 h2_months（已是统一口径）、任何实际数（ERP 管道负责）。

零头处理：按「最大余数法」逐月分配，误差摊到分，保证逐月加总与部门逐月目标分厘不差。
"""
import json, os, sys
from decimal import Decimal, ROUND_HALF_UP, ROUND_FLOOR
from collections import defaultdict

BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
UNI = os.path.join(BASE, '数据/月度目标_2026统一.json')
DASH = os.path.join(BASE, '数据/kpi_dashboard.json')
SCHEME = os.path.join(BASE, '数据/kpi_scheme.json')

ROLE_DEPT = {'dk': '大客户部', 'tz': '销售拓展部', 'ls_eng': '生命科学部', 'ls_mgr': '生命科学部'}


def cents(x):
    return Decimal(str(x)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)


def allocate(dept_months, people):
    """people: [(name, annual_target)] → {name: [6 个月金额]}"""
    S = sum(t for _, t in people)
    out = {n: [Decimal('0.00')] * 6 for n, _ in people}
    if S <= 0:
        return out
    for i in range(6):
        T = cents(dept_months[i])
        raw = {n: (T * Decimal(str(t)) / Decimal(str(S))) for n, t in people}
        base = {n: raw[n].quantize(Decimal('0.01'), rounding=ROUND_FLOOR) for n, _ in people}
        resid = int((T - sum(base.values())) * 100)  # 分为单位的待分配零头（必 ≥ 0）
        order = sorted([n for n, _ in people], key=lambda n: -(raw[n] - base[n]))
        for k in range(max(0, resid)):
            base[order[k % len(order)]] += Decimal('0.01')
        for n, _ in people:
            out[n][i] = base[n]
    return out


def rebuild_html(scheme):
    """用最新的 kpi_scheme.json 重新替换模板占位符生成看板 HTML
    （_build_kpi_scheme.py 生成 HTML 时本脚本尚未回填 H2 字段，故回填后必须重建一次）"""
    tpl_p = os.path.join(BASE, '数据/_kpi_dashboard_template.html')
    tpl = open(tpl_p, encoding='utf-8').read()
    html = tpl.replace('__SCHEME_JSON__', json.dumps(scheme, ensure_ascii=False, separators=(',', ':')))
    for ph, fp in (('__ACT_JSON__', '数据/kpi_actual_pack.json'), ('__ACTMAP_JSON__', '数据/kpi_actual_pack.json'),
                   ('__PROG_JSON__', '数据/kpi_progress_pack.json')):
        pack = json.load(open(os.path.join(BASE, fp), encoding='utf-8'))
        if ph == '__ACT_JSON__':
            val = {'roles': pack['roles']}
        elif ph == '__ACTMAP_JSON__':
            val = pack['map']
        else:
            val = pack
        html = html.replace(ph, json.dumps(val, ensure_ascii=False, separators=(',', ':')))
    left = [x for x in ('__SCHEME_JSON__', '__ACT_JSON__', '__ACTMAP_JSON__', '__PROG_JSON__') if x in html]
    assert not left, f'占位符未替换: {left}'
    dst = os.path.join(BASE, '三部门KPI关键任务积分卡看板.html')
    open(dst, 'w', encoding='utf-8').write(html)
    print('  📄 已重建看板 HTML（含口径A个人 H2 目标）: %s' % dst)


def main(apply=True):
    uni = json.load(open(UNI, encoding='utf-8'))
    dash = json.load(open(DASH, encoding='utf-8'))
    scheme = json.load(open(SCHEME, encoding='utf-8'))

    alloc = {}          # name -> [6]
    dept_of = {}        # name -> dept
    old_totals = {}     # name -> 旧 h2 合计
    for dept, dv in uni['depts'].items():
        ps = [(p['name'], float(p.get('target') or 0)) for p in dash['people'] if p['dept'] == dept]
        ps = sorted(ps, key=lambda x: -x[1])
        a = allocate(dv['h2_months'], ps)
        for n, _ in ps:
            alloc[n] = [cents(v) for v in a[n]]
            dept_of[n] = dept
    for p in dash['people']:
        old_totals[p['name']] = sum(p.get('h2_months') or [0] * 6)

    # ── 写 kpi_dashboard.json ──
    for p in dash['people']:
        n = p['name']
        if n not in alloc:
            print(f'  ⚠️ {n}：部门不在统一口径表内，跳过')
            continue
        p['h2_months'] = [float(v) for v in alloc[n]]
        p['h2_target_total'] = float(sum(alloc[n]))
        p['h2_alloc'] = 'A·按人年度目标占比分摊部门H2余额（空缺份额摊到在册人员）'
    dash['h2_people_note'] = ('个人 H2 目标口径 A（2026-09-20 用户拍板）：以「部门 H2 额度 × 个人年度目标占比」分摊，'
                              '空缺份额摊到在册人员；月内节奏沿用本部门 H2 月度节奏。部门内个人逐月加总 = 部门逐月目标。')

    # ── 写 kpi_scheme.json（积分卡人员名单）──
    ann = {p['name']: p.get('target') or 0 for p in dash['people']}
    for p in scheme.get('people', []):
        n = p['name']
        d = dept_of.get(n) or ROLE_DEPT.get(p.get('role'), '')
        if n in alloc:
            p['dept'] = d
            p['h2_months'] = [float(v) for v in alloc[n]]
            p['h2_target_total'] = float(sum(alloc[n]))
            p['annual_target'] = float(ann.get(n) or 0)
            p['h2_alloc'] = 'A·按人年度目标占比分摊部门H2余额'
        else:
            p.setdefault('dept', d)
            p['h2_months'] = [0.0] * 6
            p['h2_target_total'] = 0.0
            p['h2_alloc'] = '无个人年度目标 → 不参与分摊（待补目标）'
    scheme['meta']['h2_people_rule'] = ('个人 H2 目标口径 A：部门 H2 额度 × 个人年度目标占比，空缺份额摊到在册人员；'
                                        '月内节奏 = 部门 H2 月度节奏。')

    # ── 校验（独立复算：Decimal 精确加总）──
    errs = []
    for dept, dv in uni['depts'].items():
        names = [p['name'] for p in dash['people'] if p['dept'] == dept and p['name'] in alloc]
        for i in range(6):
            s = sum((alloc[n][i] for n in names), Decimal('0.00'))
            if s != cents(dv['h2_months'][i]):
                errs.append(f'{dept} {7+i}月 个人合计 {s} ≠ 部门 {cents(dv["h2_months"][i])}')
        st = sum((sum(alloc[n]) for n in names), Decimal('0.00'))
        if st != cents(dv['h2_total'] if 'h2_total' in dv else sum(dv['h2_months'])):
            errs.append(f'{dept} 个人 H2 合计 {st} ≠ 部门 H2 额度')
    grand = sum((sum(alloc[n]) for n in alloc), Decimal('0.00'))
    if errs:
        print('  ❌ 校验未通过：')
        for e in errs:
            print('    ', e)
        if apply:
            sys.exit(1)
    else:
        print('  ✅ 校验通过：部门内个人逐月加总 = 部门逐月目标；个人 H2 合计 = 部门 H2 额度')

    print()
    print('  %-8s %-8s %11s %11s %9s' % ('姓名', '部门', '旧H2合计(万)', '新H2合计(万)', '变动(万)'))
    total_new = Decimal('0.00')
    for dept in ['大客户部', '销售拓展部', '生命科学部']:
        print('  ── %s ──' % dept)
        for p in sorted([x for x in dash['people'] if x['dept'] == dept], key=lambda x: -x['h2_target_total']):
            n = p['name']
            nt = sum(alloc[n])
            total_new += nt
            print('  %-8s %-8s %11.1f %11.1f %9.1f' % (
                n, dept, old_totals[n] / 1e4, float(nt) / 1e4, float(nt - cents(old_totals[n])) / 1e4))
    print('  %-8s %-8s %26.1f' % ('合计', '', float(total_new) / 1e4))
    uni_h2 = cents(sum(uni['depts'][d]['h2_months'][i] for d in uni['depts'] for i in range(6)))
    print('  部门 H2 额度合计 %.1f 万 ｜ 个人合计 %.1f 万 ｜ 差 %.2f 元' % (float(uni_h2) / 1e4, float(total_new) / 1e4, float(uni_h2 - total_new)))

    if apply:
        json.dump(dash, open(DASH, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)
        json.dump(scheme, open(SCHEME, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)
        print('\n  📝 已写入 数据/kpi_dashboard.json、数据/kpi_scheme.json')
        rebuild_html(scheme)
    else:
        print('\n  （dry-run，未写文件）')
    return alloc


if __name__ == '__main__':
    main(apply='--dry' not in sys.argv)
