#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""集团归集一致性巡检（防复发守卫）—— 任一门禁不过即非 0 退出，供管道/定时任务调用。

检查项：
  ① CRM（mcp_customers.json）有 group 的行数 ≥ 阈值，且 **没有任何占位符**（/ ／ 无 暂无 - ` 数字 否 未知 n/a …）被当成集团名
  ② CRM 原始集团字段非空的行，必须要么有权威 group、要么被判定为占位符（不允许"有集团却没归集"）
  ③ 权威映射 diag_mappings.cust_to_group 无占位符值
  ④ 前端 工具/index.html 的客户行归一函数必须使用 groupKeyOf（不得再直取 c.field_c3224b）
  ⑤ 抽查：生凌集团 4 主体归入同一集团，且 2026 合计与 ERP 全档一致（±1 元）

用法：env -u PYTHONPATH /opt/homebrew/bin/python3 脚本/check_group_consistency.py
"""
import io
import json
import os
import re
import sys
import warnings

warnings.filterwarnings('ignore')
import pandas as pd  # noqa: E402

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
BAD = re.compile(r'^([/／\-`]|\d+|n/?a|na|none|nan|无|否|未|空|暂无|待定|待补|其他|未填|未知|未知集团|无集团)$', re.I)
fails = []

crm = json.load(io.open(W + '/数据/mcp_customers.json', encoding='utf-8'))['customers']
dm = json.load(io.open(W + '/数据/diag_mappings.json', encoding='utf-8'))['cust_to_group']

# ① / ③ 占位符不得当集团名
bad_crm = [c.get('name') for c in crm if BAD.match(str(c.get('group') or '').strip())]
bad_map = [k for k, v in dm.items() if BAD.match(str(v).strip())]
with_g = sum(1 for c in crm if str(c.get('group') or '').strip())
print('① CRM 行 %d ｜ 有 group %d ｜ 占位符混入 %d' % (len(crm), with_g, len(bad_crm)))
print('③ 映射表 %d 条 ｜ 占位符值 %d' % (len(dm), len(bad_map)))
if bad_crm:
    fails.append('① 集团字段混入占位符：%d 行，例 %s' % (len(bad_crm), bad_crm[:3]))
if bad_map:
    fails.append('③ 映射表混入占位符：%d 条，例 %s' % (len(bad_map), bad_map[:3]))
if with_g < 400:
    fails.append('① 归集集团行数异常偏低（%d < 400），疑似 CRM 未同步或补全脚本未跑' % with_g)

# ② 有原始集团字段却没归集（且不是占位符）
orphan = [c.get('name') for c in crm
          if str(c.get('field_c3224b') or '').strip() and not BAD.match(str(c.get('field_c3224b')).strip())
          and str(c.get('field_c3224b')).strip() != str(c.get('name') or '').strip()   # 自映射=无集团，不算漏归集
          and not str(c.get('group') or '').strip()]
print('② CRM 有原始集团但未归集：%d 行' % len(orphan))
if orphan:
    fails.append('② 有集团却未归集：%d 行，例 %s' % (len(orphan), orphan[:3]))

# ④ 前端不得直取原始字段当集团
html = io.open(W + '/工具/index.html', encoding='utf-8').read()
raw_use = len(re.findall(r'group:\s*c\.field_c3224b', html))
print('④ 前端直取 field_c3224b 当集团：%d 处（应为 0）' % raw_use)
if raw_use:
    fails.append('④ 前端仍直取原始集团字段（groupKeyOf 未生效）')

# ⑤ 生凌抽查 + 与 ERP 全档勾稽
sl = [c for c in crm if '生凌' in str(c.get('name'))]
sl_grp = {str(c.get('group') or '') for c in sl}
try:
    sys.path.insert(0, W + '/脚本')
    import erp_sales_core
    erp = pd.read_excel(erp_sales_core.find_latest_erp())   # 不硬编码文件名，始终取最新合并件
    sub = erp[erp['客户'].astype(str).str.contains('生凌', na=False)]
    amt = pd.to_numeric(sub['价税合计'], errors='coerce').fillna(0)
    gift = sub.get('是否赠品', pd.Series([''] * len(sub))).astype(str).str.contains('是', na=False).tolist()
    erp_2026 = sum(a for a, g in zip(amt.tolist(), gift) if not g)
    key = json.load(io.open(W + '/数据/pricing/_key_customers.json', encoding='utf-8'))
    core = sum((r.get('sales_2026') or 0) for r in key if '生凌' in str(r.get('name')) and not r.get('is_group_row'))
    print('⑤ 生凌：CRM %d 家 → 集团 %s ｜ 工作台 2026 合计 %.2f 元 ｜ ERP %.2f 元 ｜ 差 %.2f'
          % (len(sl), sl_grp, core, erp_2026, core - erp_2026))
    if sl_grp != {'广州生凌集团'}:
        fails.append('⑤ 生凌集团归集异常：%s' % sl_grp)
    if abs(core - erp_2026) > 1:
        fails.append('⑤ 生凌 2026 合计与 ERP 不符：工作台 %.2f vs ERP %.2f' % (core, erp_2026))
except Exception as e:
    print('⑤ 勾稽跳过：%s' % str(e)[:80])

print()
if fails:
    print('❌ 巡检未通过：')
    for f in fails:
        print('   -', f)
    sys.exit(1)
print('✅ 集团归集巡检全部通过')
