#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""归属修正硬验收（全部真跑，不估算）。"""
import json, os, sys, subprocess, time
W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
D = f'{W}/数据/三部门逐月_2026.json'
BK_NOW = f'{W}/归档/_版本备份/_bak_ownerfix2_20261006_212914/三部门逐月_2026.json'          # 本轮改前
BK_PREYAW = f'{W}/数据/三部门逐月_2026.json.bak_20261006_210222'             # 雅为修正前
TGT = f'{W}/.q3scratch/owner_fix_targets.json'

new = json.load(open(D, encoding='utf-8'))
now = json.load(open(BK_NOW, encoding='utf-8'))
prey = json.load(open(BK_PREYAW, encoding='utf-8'))
tgt = json.load(open(TGT, encoding='utf-8'))
EXPECT_OWNED = {r['cust']: r['dominant'] for r in tgt['owned']}
EXPECT_UNOWNED = {r['cust'] for r in tgt['unowned']}
ok = True
def chk(name, cond, detail=''):
    global ok
    print(f"  {'✅' if cond else '❌'} {name}{(' — ' + detail) if detail else ''}")
    if not cond: ok = False

def sales_of(J):
    return {(c['month'], c['dept'], c['cust']): c.get('sales') for c in J['customers']}

ns, ws, ys = sales_of(new), sales_of(now), sales_of(prey)

print("=" * 78)
print("① 改动覆盖面：相对「本轮改前备份」逐行 diff 归属人")
changed_now = sorted({k[2] for k in ns if ns[k] != ws.get(k)})
expected_change = sorted(c for c in EXPECT_OWNED if c != '深圳市雅为世纪科技有限公司')
chk("变更客户集合 == 预期（14 家；雅为世纪本轮改前已是陈溪子，故相对本轮备份不变）",
    changed_now == expected_change, f"changed={len(changed_now)} expected={len(expected_change)}")
if changed_now != expected_change:
    print("     diff改=", changed_now); print("     expected=", expected_change)

print("\n  相对「雅为修正前备份」逐行 diff（应= 全部 15 家覆盖键）")
changed_pre = sorted({k[2] for k in ns if ns[k] != ys.get(k)})
chk("变更客户集合 == 15 家覆盖键（含雅为世纪）", changed_pre == sorted(EXPECT_OWNED),
    f"changed={len(changed_pre)}")
if changed_pre != sorted(EXPECT_OWNED):
    print("     diff改=", changed_pre)

print("\n② 逐人 diff 被改销售员列表与预期完全一致")
PRE_APPLIED = {'深圳市雅为世纪科技有限公司'}   # 本轮之前已改（雅为世纪 上一轮 刘子研→陈溪子）
for cust in sorted(EXPECT_OWNED):
    old = sorted({ws.get(k) for k in ws if k[2] == cust})
    newv = sorted({ns[k] for k in ns if k[2] == cust})
    want = EXPECT_OWNED[cust]
    if cust in PRE_APPLIED:
        chk(f"{cust}: {old} -> {newv}（预期 {want}；本轮前已改，相对本轮备份不变）",
            newv == [want] and old == [want])
    else:
        chk(f"{cust}: {old} -> {newv}（预期 {want}）", newv == [want] and (old != [want]),
            '' if newv == [want] else 'MISMATCH')

print("\n③ 46 家无归属客户一个都没动（集合 + 行数）")
un_now = {k[2] for k in ns if not ns[k]}
un_before = {k[2] for k in ws if not ws[k]}
un_prey = {k[2] for k in ys if not ys[k]}
chk("无归属客户集合 改前=改后", un_now == un_before)
chk("无归属行数 改前=改后", sum(1 for k in ns if not ns[k]) == sum(1 for k in ws if not ws[k]),
    f"{sum(1 for k in ws if not ws[k])} -> {sum(1 for k in ns if not ns[k])}")
chk("46 家待放权清单 ⊂ 无归属集合（一家都没被顺手改）", EXPECT_UNOWNED <= un_now,
    f"缺失={sorted(EXPECT_UNOWNED - un_now)}")
chk("无归属与雅为修正前一致", un_now == un_prey)

print("\n④ 金额守恒（实体口径 = 集团口径）")
c26 = round(sum(c.get('amt') or 0 for c in new['customers']), 2)
g26 = round(sum(c['amt'] for c in new['customers_grp']), 2)
c25 = round(sum(c.get('amt_2025') or 0 for c in new['customers']), 2)
g25 = round(sum(c['amt_2025'] for c in new['customers_grp']), 2)
chk("Σcustomers = 3442.93", c26 == 3442.93, f"{c26}")
chk("Σcustomers_grp = 3442.93", g26 == 3442.93, f"{g26}")
chk("Σcustomers == Σcustomers_grp", abs(c26 - g26) < 0.01)
chk("Σamt_2025 = 2749.29（实体）", c25 == 2749.29, f"{c25}")
chk("Σamt_2025 = 2749.29（集团）", g25 == 2749.29, f"{g25}")

print("\n   customers_grp 的 sales_list / mixed 重建正确性")
# 重算集团口径，与文件比对
buck = {}
for c in new['customers']:
    key = c.get('grp') or c.get('cust')
    k = (c['month'], c['dept'], key)
    b = buck.setdefault(k, {'amt': 0.0, 'amt25': 0.0, 'mem': set(), 'own': set()})
    b['amt'] += c.get('amt') or 0.0; b['amt25'] += c.get('amt_2025') or 0.0
    b['mem'].add(c.get('cust')); b['own'].add(c.get('sales') or '(无归属)')
bad = 0
for r in new['customers_grp']:
    k = (r['month'], r['dept'], r['cust'])
    b = buck.get(k)
    if b is None:
        bad += 1; continue
    if sorted(r['sales_list']) != sorted(b['own']) or r['mixed'] != (len(b['own']) > 1):
        bad += 1
chk("group_rows 的 sales_list/mixed 与重算一致", bad == 0, f"不一致 {bad}/{len(new['customers_grp'])}")

print("\n⑤ 刘子研线回归")
sp_rows = [r for r in new['salespersons'] if r.get('sp') == '刘子研']
lzy_sp_amt = round(sum(r.get('amt') or 0 for r in sp_rows), 2)
lzy_c = [c for c in new['customers'] if c.get('sales') == '刘子研']
lzy_c_amt = round(sum(c.get('amt') or 0 for c in lzy_c), 2)
chk("刘子研 salespersons 额 = 644.11", lzy_sp_amt == 644.11, f"{lzy_sp_amt}")
chk("刘子研 customers 额 = 642.40", lzy_c_amt == 642.40, f"{lzy_c_amt}")
# 这 15 家里是否涉及刘子研
inv = [c for c in EXPECT_OWNED if EXPECT_OWNED[c] == '刘子研']
pre_owned_lzy = [c for c in now['customers'] if c.get('sales') == '刘子研']
pre_lzy_amt = round(sum(c.get('amt') or 0 for c in pre_owned_lzy), 2)
chk("这 15 家中无新归属到刘子研的客户", inv == [], f"{inv}")
print(f"     （备注：雅为世纪在本轮之前已由 刘子研→陈溪子 移出 0.99 万；本轮无刘子研相关变更）")

print("\n⑥ 目标体系不受影响")
tm = new['total']['target_monthly']
chk("全年目标合计 = 6650.01", round(sum(tm.values()), 2) == 6650.01, f"{round(sum(tm.values()),2)}")
chk("Q4 目标 = 1913.6", new['q4'].get('target') == 1913.6, f"{new['q4'].get('target')}")
chk("年度缺口 = 1293.6", new['q4'].get('annual_shortfall_at_full_q4') == 1293.6,
    f"{new['q4'].get('annual_shortfall_at_full_q4')}")
# 目标体系与改前一致
chk("total/months/depts 与改前逐项一致",
    json.dumps(new['total'], sort_keys=True) == json.dumps(now['total'], sort_keys=True)
    and json.dumps(new['depts'], sort_keys=True) == json.dumps(now['depts'], sort_keys=True))
chk("salespersons 全量（含目标）与改前逐项一致",
    json.dumps(new['salespersons'], sort_keys=True, ensure_ascii=False)
    == json.dumps(now['salespersons'], sort_keys=True, ensure_ascii=False))

print("\n⑦ 边界标注落盘")
mf = new['meta'].get('customer_owner_override', {})
chk("meta.override_count = 15", mf.get('override_count') == 15, f"{mf.get('override_count')}")
chk("meta.edge_flags 含潮州凯普(low_confidence)",
    mf.get('edge_flags', {}).get('潮州凯普生物化学有限公司') == 'low_confidence')
chk("meta.edge_flags 含优联瑞康(owner_is_manager)",
    mf.get('edge_flags', {}).get('优联瑞康(上海)基因科技有限公司') == 'owner_is_manager')
chk("meta.near_tie_notes 记录 2 条近似平局", len(mf.get('near_tie_notes', {})) == 2)

print("\n" + "=" * 78)
print("ACCEPTANCE:", "ALL PASSED" if ok else "FAILED")
sys.exit(0 if ok else 1)
