#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
把 GLM 提取的特殊要求候选 → 复核 → 合并进正式库
====================================================
数据/customer_special_requirements.json 是正式库（页面/接口直接读它）。
本脚本只做两件事，都不改正式库，直到显式 --apply：

  --review            统计 + 生成复核件（数据/sr_review.json + 工具/特殊要求复核.html）
  --apply [--min 高]   把通过校验的候选项并入正式库（自动备份；默认只并「高」置信度）
  --apply-file X.json 按页面导出的审核结果（含 采纳/驳回 标记）并入
"""
import argparse
import json
import os
import re
import shutil
import sys
from datetime import datetime

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DA = os.path.join(ROOT, '数据')
LIB = os.path.join(DA, 'customer_special_requirements.json')
EXTRACT = os.path.join(DA, '_sr_extracted.json')
REVIEW = os.path.join(DA, 'sr_review.json')
CATS = ['搭配规则', '配方/体系', '包装/标签', '交付/物流', '质检/文件', '价格/账期', '其他']

# 明显不是「特殊要求」的句式（客诉、单纯进展、寒暄）→ 复核件里标注，不自动入库
NEG_PAT = re.compile(r'(投诉|客诉|质量问题|要求赔偿|退货|换货|不回复|未回复|已拜访|拜访了|电话沟通|微信沟通|表示满意|暂无|没有问题)')


def norm(s):
    return re.sub(r'[\s，。、；：（）()【】\[\]“”"\'’]+', '', str(s or ''))


def load_extracted():
    if not os.path.exists(EXTRACT):
        print('  ⚠️ 未找到 %s' % EXTRACT)
        return []
    return json.load(open(EXTRACT, encoding='utf-8')).get('items') or []


def validate(it, act_index):
    """返回 (ok, 问题列表, 规范化后的条目)"""
    probs = []
    cust = str(it.get('客户') or '').strip()
    cat = str(it.get('类别') or '').strip()
    title = str(it.get('标题') or '').strip()
    content = str(it.get('内容') or '').strip()
    ev = str(it.get('依据') or '').strip()
    conf = str(it.get('置信度') or '').strip()
    aid = str(it.get('活动id') or '').strip()
    skus = it.get('涉及货号') or []
    if not cust:
        probs.append('缺客户')
    elif cust.strip().lower() in ('nan', 'none', 'null', '-', '/') or len(cust.strip()) < 3:
        probs.append('客户名无效（脏数据）')
    if cat not in CATS:
        cat = '其他'
        probs.append('类别修正为其他')
    if not content or len(content) < 8:
        probs.append('内容过短')
    if not ev:
        probs.append('缺依据（无法追溯）')
    if NEG_PAT.search(content):
        probs.append('疑似客诉/进展，非特殊要求')
    if conf not in ('高', '中', '低'):
        conf = '中'
        probs.append('置信度缺失')
    a = act_index.get(aid) or {}
    return (not probs), probs, {
        'customer': cust, 'category': cat, 'title': title, 'content': content,
        'skus': [str(x).strip() for x in skus if str(x).strip()] if isinstance(skus, list) else [],
        'evidence': ev, 'confidence': conf, 'activity_id': aid,
        'activity_date': a.get('date') or '', 'activity_owner': a.get('owner') or '',
        'activity_type': a.get('type') or '',
    }


def build_review():
    items = load_extracted()
    acts = {}
    p = os.path.join(DA, '_sr_input.json')
    if os.path.exists(p):
        for a in (json.load(open(p, encoding='utf-8')).get('candidates') or []):
            acts[a['id']] = a
    rows, seen = [], set()
    for it in items:
        ok, probs, r = validate(it, acts)
        key = (r['customer'], r['category'], norm(r['content'])[:40])
        if key in seen:
            continue
        seen.add(key)
        r['ok'] = ok
        r['problems'] = probs
        rows.append(r)
    rows.sort(key=lambda x: (x['customer'], x['category']))
    from collections import Counter
    by_cust = Counter(r['customer'] for r in rows)
    by_cat = Counter(r['category'] for r in rows)
    by_conf = Counter(r['confidence'] for r in rows)
    out = {
        'generated': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        'source': '智谱 GLM 从飞书活动记录提取（2026-01-04 ~ 2026-09-19，11,204 条去重活动）',
        'total_raw': len(items), 'total_after_dedupe': len(rows),
        'clean': sum(1 for r in rows if r['ok']), 'with_problems': sum(1 for r in rows if not r['ok']),
        'by_customer': len(by_cust), 'by_category': dict(by_cat), 'by_confidence': dict(by_conf),
        'top_customers': by_cust.most_common(15),
        'rows': rows,
    }
    json.dump(out, open(REVIEW, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
    print('  原始抽出 %d 条 → 去重后 %d 条（客户 %d 个）' % (out['total_raw'], out['total_after_dedupe'], out['by_customer']))
    print('  干净可入库 %d ｜ 有问题 %d' % (out['clean'], out['with_problems']))
    print('  按类别: %s' % '、'.join('%s %d' % (k, v) for k, v in by_cat.most_common()))
    print('  按置信度: %s' % '、'.join('%s %d' % (k, v) for k, v in by_conf.most_common()))
    print('  候选最多的客户: %s' % '、'.join('%s(%d)' % (k, v) for k, v in by_cust.most_common(8)))
    print('  → 复核件: %s' % REVIEW)
    return out


def next_ids(n):
    d = datetime.now().strftime('%Y%m%d')
    return ['sr_%s_%04d' % (d, i + 1) for i in range(n)]


def apply_items(rows, note_suffix=''):
    lib = json.load(open(LIB, encoding='utf-8'))
    have = lib.get('items') or []
    exist = {(i.get('customer'), norm(i.get('content'))[:40]) for i in have}
    exist_title = {(i.get('customer'), i.get('category'), norm(i.get('title'))[:20]) for i in have}
    add = []
    for r in rows:
        if (r['customer'], norm(r['content'])[:40]) in exist:
            continue
        if (r['customer'], r['category'], norm(r.get('title'))[:20]) in exist_title:
            continue
        add.append(r)
    if not add:
        print('  没有新条目需要并入')
        return 0
    ids = next_ids(len(add))
    now = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
    for r, i in zip(add, ids):
        src = '飞书活动 %s（%s，%s）' % (r['activity_date'] or '日期未知', r['activity_id'] or '无ID', r['confidence'] + '置信度')
        have.append({
            'id': i, 'customer': r['customer'], 'category': r['category'],
            'title': r['title'] or r['content'][:16], 'content': r['content'],
            'skus': r['skus'],
            # 高置信度=正式生效；中/低=待复核（页面可见但标待确认，避免把没把握的要求当依据用）
            'status': 'active' if r.get('confidence') == '高' else 'pending',
            'created_by': '智谱GLM提取',
            'owner': (r.get('activity_owner') or '').strip(),   # 活动经手人（邮箱或姓名），用于归属映射未覆盖客户的判权兜底
            'created_at': now, 'updated_at': now,
            'source': src, 'note': ('依据原文：%s' % r['evidence'])[:300] + (note_suffix or ''),
        })
    shutil.copy2(LIB, LIB + '.bak_' + datetime.now().strftime('%Y%m%d_%H%M%S'))
    lib['items'] = have
    lib['updated_at'] = now
    lib['version'] = int(lib.get('version') or 1) + 1
    json.dump(lib, open(LIB, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
    print('  ✅ 并入 %d 条 → 库现有 %d 条（version %s）' % (len(add), len(have), lib['version']))
    return len(add)


if __name__ == '__main__':
    ap = argparse.ArgumentParser()
    ap.add_argument('--review', action='store_true')
    ap.add_argument('--apply', action='store_true')
    ap.add_argument('--min', default='高', choices=['高', '中', '低'])
    ap.add_argument('--apply-file', default='')
    a = ap.parse_args()
    if a.apply or a.apply_file:
        if a.apply_file:
            d = json.load(open(a.apply_file, encoding='utf-8'))
            rows = [r for r in (d.get('rows') or d.get('items') or []) if r.get('adopt') in (True, 'true', '1', 1, '采纳')]
            print('  按复核结果并入：采纳 %d 条' % len(rows))
        else:
            rv = build_review() if not os.path.exists(REVIEW) else json.load(open(REVIEW, encoding='utf-8'))
            order = {'高': 3, '中': 2, '低': 1}
            floor = order[a.min]
            rows = [r for r in rv['rows'] if r['ok'] and order[r['confidence']] >= floor]
            print('  按置信度≥%s 并入：%d 条' % (a.min, len(rows)))
        apply_items(rows)
    if a.review or not (a.review or a.apply or a.apply_file):
        build_review()
