#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成【唯一权威目标源】数据/目标基础数据_2026.json（种子）。

设计：从当前权威状态原样种入，**不得漂移**——
  · 部门年度/月度目标 ← 数据/月度目标_2026统一.json（元 → 万元；= 看板现用口径）
  · 生命科学部 广东/浙江 ← 各 300 万，月度 = 部门当月 / 2（12 月吸收舍入差，合计精确 300）
  · 人员（部门/岗位名/所属区域/年度目标/在职状态/离职归属）← 2026基础数据核对表HTML 内嵌 DATA（派生自
    三部门逐月_2026.json + kpi_scheme.json + sp_merge.py，已与用户拍板口径一致）
  · 人员月度目标 = 按规则(默认 shape) 从年度摊派，合计精确 = 年度；无源月 null

用法：/usr/bin/python3 脚本/build_target_source.py [--rule shape|even|manual]
"""
import datetime
import io
import json
import os
import re
import sys

W = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(W, '脚本'))
import target_source as T          # noqa: E402  （taper / MONTHS）
import ls_region_map as LR         # noqa: E402

LEGACY = os.path.join(W, '数据', '月度目标_2026统一.json')
REVIEW_HTML = os.path.join(W, '2026基础数据核对表_全部部门_可修正.html')
OUT = os.path.join(W, '数据', '目标基础数据_2026.json')
DEPTS = ['大客户部', '销售拓展部', '生命科学部']
LS_REGION_BUDGET = {'广东': 300.0, '浙江': 300.0}
LS_REGION_MANAGER = {'广东': '韩远怀', '浙江': '吴云'}
LS_PERSON_SHORTFALL = {'韩远怀': 65.0, '吴云': 110.0}
# 部门「未分摊」→ 指定负责人（用户 2026-10-06 指定）：拓展部未分摊 430 万整笔计入刘欣
UNALLOCATED_TO_PERSON = {'销售拓展部': '刘欣'}


def load_roster():
    html = io.open(REVIEW_HTML, encoding='utf-8').read()
    m = re.search(r'<script id="DATA" type="application/json">(.*?)</script>', html, re.S)
    if not m:
        raise SystemExit('❌ 核对表 HTML 未找到内嵌 DATA')
    return json.loads(m.group(1))


def norm_status(s):
    s = str(s or '')
    if '离职' in s:
        return '离职'
    if '新增' in s:
        return '新增'
    return '在职'


def dept_months_from_legacy():
    t = json.load(io.open(LEGACY, encoding='utf-8'))
    out = {}
    for d in DEPTS:
        ms = list(t['depts'][d]['months'])
        assert len(ms) == 12
        out[d] = {T.MONTHS[i]: round(ms[i] / T.WAN, 2) for i in range(12)}
    return out, {d: t['depts'][d]['annual'] for d in DEPTS}


def region_monthly(dept_m):
    """区域月目标 = 部门当月 / 2（万元），12 月吸收舍入差使各区合计精确 300。"""
    out = {}
    for rg in ('广东', '浙江'):
        vals = {m: round(dept_m[m] / 2.0, 2) for m in T.MONTHS}
        keys = list(T.MONTHS)
        s = round(sum(vals[m] for m in keys[:-1]), 2)
        vals[keys[-1]] = round(300.0 - s, 2)
        out[rg] = vals
    return out


def build(rule='shape'):
    D = load_roster()
    rows = D['rows']
    legacy_dept_m, legacy_dept_a = dept_months_from_legacy()

    departments = {}
    for d in DEPTS:
        dep = {'target_annual': float(legacy_dept_a[d]),
               'target_monthly': dict(legacy_dept_m[d])}
        if d == '生命科学部':
            rm = region_monthly(legacy_dept_m[d])
            dep['regions'] = {}
            for rg, mgr in LS_REGION_MANAGER.items():
                dep['regions'][rg] = {'target_annual': LS_REGION_BUDGET[rg],
                                      'target_monthly': rm[rg], 'manager': mgr}
        departments[d] = dep

    people = []
    for r in rows:
        name = r['name']
        dept = r.get('dash_dept')
        status = norm_status(r.get('status'))
        post = (r.get('region') or '').strip()
        post = '' if post in ('—', '-') else post
        ann = r.get('target_annual')
        if dept == '生命科学部':
            region = LR.region_of(name) or ''
        else:
            region = ''
        if name == '张晋菲':
            region = '浙江'          # 新增人员，浙江第 5 人
        p = {'name': name, 'dept': dept, 'region': region, 'post': post,
             'target_annual': (round(float(ann), 2) if ann is not None else None),
             'target_monthly': None, 'status': status,
             'count_in_stats': status in ('在职', '新增'),
             'merged_into': r.get('merged_into'), 'manager_shortfall': None,
             'note': (r.get('note') or '')}
        if name in LS_PERSON_SHORTFALL:
            p['manager_shortfall'] = LS_PERSON_SHORTFALL[name]
        people.append(p)

    # 王昕伟：去年离职、无 2026 订单（仅记录说明，不计入统计）
    people.append({'name': '王昕伟', 'dept': '销售拓展部', 'region': '', 'post': '',
                   'target_annual': None, 'target_monthly': None, 'status': '离职',
                   'count_in_stats': False, 'merged_into': None, 'manager_shortfall': None,
                   'note': '去年离职、无 2026 订单 → 仅记录说明，不做任何金额动作'})

    # 部门「未分摊」→ 指定负责人：先按现有逐人目标算未分摊，再整笔落到指定人，
    # 使该部门 Σ(计入统计且有目标的人) 精确 = 部门年度（未分摊归 0）。
    # 用户 2026-10-06 指定：销售拓展部未分摊 430 万 → 刘欣（部门负责人）。
    _raw = {d: round(sum(p['target_annual'] for p in people
                         if p['dept'] == d and p['count_in_stats'] and p['target_annual'] is not None), 2)
            for d in DEPTS}
    for _d, _nm in UNALLOCATED_TO_PERSON.items():
        _un = round(float(legacy_dept_a[_d]) - _raw[_d], 2)
        if not _un:
            continue
        for _p in people:
            if _p['name'] == _nm:
                _p['target_annual'] = _un
                _p['note'] = ((_p['note'] + '；') if _p['note'] else '') + \
                    '承担本部门未分摊 %s 万（用户 2026-10-06 指定计入）' % _un
                break

    # 人员月度目标：按规则从年度摊派（LS 按区域形态、其余按部门形态），合计精确 = 年度
    for p in people:
        if p['target_annual'] is None:
            p['target_monthly'] = None
            continue
        shape = (departments[p['dept']].get('regions', {}).get(p['region'], {}) or {}).get('target_monthly') \
            or departments[p['dept']]['target_monthly']
        p['target_monthly'] = T.taper(p['target_annual'], shape, rule)

    # 每部门「未分摊」= 部门年度 − Σ(计入统计且有年度目标的人年度)（管理预留/未分到人）
    for d in DEPTS:
        s = round(sum(p['target_annual'] for p in people
                      if p['dept'] == d and p['count_in_stats'] and p['target_annual'] is not None), 2)
        departments[d]['unallocated'] = round(float(legacy_dept_a[d]) - s, 2)

    sd = {
        'meta': {
            'authoritative': True,
            'name': '2026 目标基础数据（唯一权威源）',
            'version': 1,
            'generated': datetime.datetime.now().astimezone().isoformat(timespec='seconds'),
            'author': '目标基础数据管理台（工具/仪表盘/目标基础数据管理台.html 导出落盘）',
            'supersedes': ['数据/kpi_scheme.json', '数据/月度目标_2026统一.json'],
            'monthly_rule': rule,
            'months': T.MONTHS,
            'total_annual': round(sum(departments[d]['target_annual'] for d in DEPTS), 2),
            'scale': '万元',
            'note': ('本文件是目标数据的唯一权威源：部门/区域/个人年度目标与月度目标。'
                     '改动流程＝管理台页面编辑 → 导出 JSON → 落盘本文件 → 重跑 脚本/refresh_sandept.sh → 看板更新。'
                     '月度目标由 annual 按 monthly_rule（shape/even/manual）自动摊派，合计精确等于年度；无源月为 null。'
                     '旧源 kpi_scheme.json / 月度目标_2026统一.json 已降为历史（deprecation），保留不删。'
                     '【分摊终结】销售拓展部声明 3,400 万、Σ人原仅 2,970 万，差额 430 万已整笔计入'
                     ' 刘欣（liuxin@biori.com，部门负责人）→ 该部门 Σ人 = 3,400 万、未分摊归 0（用户 2026-10-06 指定）。'),
            'closure_rule': ('区域年度 = Σ区域内人；部门年度 = Σ人 + 未分摊；全司年度 = Σ部门年度。'
                             '销售拓展部未分摊 430 万已计入刘欣（用户 2026-10-06 指定），'
                             '该部门未分摊现为 0，三级闭合终结（拓展部 Σ人=3400、全司 Σ部门=6650）。'),
        },
        'departments': departments,
        'people': people,
    }
    return sd


def main():
    rule = 'shape'
    if '--rule' in sys.argv:
        rule = sys.argv[sys.argv.index('--rule') + 1]
    assert rule in T.RULES, 'rule 必须是 %s' % (T.RULES,)
    sd = build(rule)
    if os.path.exists(OUT):
        import shutil
        shutil.copy2(OUT, OUT + '.bak')
    with io.open(OUT, 'w', encoding='utf-8') as f:
        json.dump(sd, f, ensure_ascii=False, indent=1)

    ok, rep = T.check_closure(T.load())
    print('已写出', OUT, '（rule=%s）' % rule)
    print(json.dumps(rep, ensure_ascii=False, indent=1))
    print('三级闭合自检:', 'PASS' if ok else 'FAIL')
    # 关键值为 0 数量（无源月必须 null，不得 0）
    zeros = 0
    def _scan(x):
        global zeros
        if isinstance(x, dict):
            for k, v in x.items():
                if ('monthly' in str(k) or str(k).startswith('2026-')) and v == 0:
                    zeros += 1
                _scan(v)
        elif isinstance(x, list):
            for v in x:
                _scan(v)
    _scan(sd)
    print('月度目标中 ==0 的键数（应为 0）:', zeros)
    return 0 if ok else 1


if __name__ == '__main__':
    sys.exit(main())
