#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""行业信息 v0.1 · 增强器：逐条抓正文 → DeepSeek 提炼五要素。

输入：数据/market_intel/runs/<date>_items.jsonl
输出：数据/market_intel/runs/<date>_enriched.jsonl（每条追加 五要素 字段）

引擎：DeepSeek（config.yaml 取 key，不落盘打印）；并发 4；失败重试 1 次。
要点口径：时间 / 地点 / 人物或主体 / 事件 / 关键事项；仅依据正文，缺项写「未提及」。
"""
import json
import os
import re
import sys
import time
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta

import requests

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
RUNS = os.path.join(W, '数据/market_intel/runs')
DAY = datetime.now().strftime('%Y%m%d')
MODE = 'daily'
for a in sys.argv[1:]:
    if a in ('first', 'daily'):
        MODE = a
    elif a.isdigit():
        DAY = a
SRC_FILE = DAY + ('_items_first.jsonl' if MODE == 'first' else '_items.jsonl')
OUT_FILE = DAY + ('_enriched_first.jsonl' if MODE == 'first' else '_enriched.jsonl')
CFG = os.path.expanduser('~/.hermes/profiles/zone-b/config.yaml')

# ── 取 DeepSeek key ──
txt = open(CFG, encoding='utf-8').read()
m = re.search(r'base_url:\s*(https://api\.deepseek\.com)', txt)
KEY = re.search(r'api_key:\s*(sk-[A-Za-z0-9]+)', txt)
API = 'https://api.deepseek.com/chat/completions'
MODEL = 'deepseek-flash'
if not KEY:
    print('❌ 未取到 API key'); sys.exit(1)

UA = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 '
      '(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36')
S = requests.Session()
S.headers.update({'User-Agent': UA, 'Accept-Language': 'zh-CN,zh;q=0.9'})

PROMPT = """你是产业情报分析员。请只依据下面的正文，提炼这条信息的核心要点。
要求：
1. 严格依据正文，不得推测、不得补充正文以外的知识；正文没有的写「未提及」。
2. 输出 JSON（不要任何多余文字），字段：
{"时间":"","地点":"","人物":"","主体":"","事件":"","关键事项":"","与我方关联":""}
3. 「时间」尽量精确到日；「地点」写国家/城市或"线上"；「人物」写被提到的人名与职务（没有则未提及）；
「主体」写涉事机构/品牌；「事件」一句话（≤40 字）；「关键事项」用「；」分隔 2–4 条要点（每条 ≤30 字）；
「与我方关联」= 对分子诊断原料供应商（酶/预混液/冻干体系/引物探针）的机会或威胁，一句话，没有则写「无直接关联」。

标题：{title}
来源：{src}　主体：{brand}　发布日期：{date}
正文：
{body}
"""


def fetch_text(url, kind):
    if not url:
        return ''
    try:
        if url.lower().endswith('.pdf'):
            r = S.get(url, timeout=40)
            tmpd = os.environ.get('TMPDIR', '/tmp')
            p = os.path.join(tmpd, '_intel_%08d.pdf' % (abs(hash(url)) % 10 ** 8))
            open(p, 'wb').write(r.content)
            import fitz
            doc = fitz.open(p)
            return re.sub(r'\s+', ' ', '\n'.join(doc[i].get_text() for i in range(min(3, len(doc)))))[:2600]
        r = S.get(url, timeout=25)
        r.encoding = r.apparent_encoding or r.encoding
        h = re.sub(r'(?is)<(script|style|nav|footer|head)[^>]*>.*?</\1>', ' ', r.text)
        t = re.sub(r'(?s)<[^>]+>', ' ', h)
        t = re.sub(r'&[a-z]+;|&#\d+;', ' ', t)
        return re.sub(r'\s+', ' ', t)[:2600]
    except Exception as e:
        return '[抓取失败: %s]' % str(e)[:60]


def summarize(it):
    body = it.get('_body') or fetch_text(it['链接'], it['来源'])
    if not body or body.startswith('[抓取失败'):
        body = (it.get('摘要') or '') + ' （正文抓取失败，仅据标题与摘要提炼）'
    prompt = (PROMPT.replace('{title}', it['标题']).replace('{src}', it['来源'])
                    .replace('{brand}', it['主体']).replace('{date}', it.get('日期', ''))
                    .replace('{body}', body[:2600]))
    for attempt in range(2):
        try:
            r = requests.post(API, timeout=120,
                              headers={'Authorization': 'Bearer ' + KEY.group(1),
                                       'Content-Type': 'application/json'},
                              json={'model': MODEL, 'temperature': 0,
                                    'max_tokens': 3000 if attempt == 0 else 4500,
                                    'messages': [{'role': 'user', 'content': prompt}]})
            j = r.json()
            ch = (j.get('choices') or [{}])[0]
            c = (ch.get('message', {}).get('content') or '').strip()
            if not c:
                # 推理占满额度 → 升上限重试
                raise RuntimeError('空内容 finish=%s reasoning=%s' % (
                    ch.get('finish_reason'), (j.get('usage') or {}).get('completion_tokens_details')))
            c = re.sub(r'^```(?:json)?|```$', '', c.strip(), flags=re.M).strip()
            m2 = re.search(r'\{.*\}', c, re.S)
            return json.loads(m2.group(0)) if m2 else {'错误': c[:80]}
        except Exception as e:
            if attempt == 0:
                time.sleep(2.5)
            else:
                return {'错误': str(e)[:80]}
    return {'错误': 'unknown'}


items = [json.loads(l) for l in open(os.path.join(RUNS, SRC_FILE), encoding='utf-8') if l.strip()]
print('模式 %s ｜ 源文件 %s ｜ 条目 %d' % (MODE, SRC_FILE, len(items)))
RECENT = (datetime.now() - timedelta(days=45)).strftime('%Y-%m-%d')

# 选要提炼的子集：竞品有日期 + 客户业务相关 + 招投标
BIZ = re.compile(r'注册证|批准|中标|合同|产能|专利|临床|GMP|收购|增资|投资|业绩|年报|半年报|季度|新品|上市|合作|签约|扩产|获证|CE|FDA|IVDR')
EXIST = []
if os.path.exists(os.path.join(RUNS, OUT_FILE)):
    EXIST = json.load(open(os.path.join(RUNS, OUT_FILE), encoding='utf-8'))
done_links = {i['链接'] for i in EXIST if isinstance(i.get('要点'), dict) and '错误' not in i['要点']}
sel = []
CAP = 700 if MODE == 'first' else 200
for i in items:
    if i['来源'].startswith('竞品官网'):
        sel.append(i)                       # 竞品全量（含三年窗口）
    elif i['来源'] == '巨潮公告' and BIZ.search(i['标题']):
        sel.append(i)                       # 客户侧只提炼业务相关（注册证/合作/中标等）
    elif i['来源'].startswith(('全国公共资源交易', '检索源')):
        sel.append(i)
before = len(sel)
sel = [i for i in sel if i['链接'] not in done_links][:CAP]
if before != len(sel):
    print('幂等跳过已提炼 %d 条（复用历史结果，不重烧）' % (before - len(sel)))
print('待提炼条目: %d（竞品 %d ／ 客户公告 %d ／ 招投标 %d）' % (
    len(sel), sum(1 for i in sel if i['来源'].startswith('竞品官网')),
    sum(1 for i in sel if i['来源'] == '巨潮公告'),
    sum(1 for i in sel if i['来源'].startswith(('全国公共资源', '检索源')))))

if not sel:
    print('✅ 无新增条目，跳过提炼（幂等）')
    raise SystemExit(0)

t0 = time.time()
with ThreadPoolExecutor(max_workers=4) as ex:
    outs = list(ex.map(summarize, sel))
for it, o in zip(sel, outs):
    it['要点'] = o
ok = sum(1 for o in outs if '错误' not in o)
NEW = [{k: v for k, v in it.items() if k != '_body'} for it in sel]
have = {i['链接'] for i in EXIST}
MERGED = EXIST + [x for x in NEW if x['链接'] not in have]
json.dump(MERGED, open(os.path.join(RUNS, OUT_FILE), 'w', encoding='utf-8'),
          ensure_ascii=False, indent=1)
print('合并后文件条目: %d（历史 %d + 新增 %d）' % (len(MERGED), len(EXIST), len(MERGED) - len(EXIST)))
print('✅ 提炼完成: %d/%d 成功 ｜ 用时 %.0f 秒' % (ok, len(sel), time.time() - t0))
print('→', os.path.join(RUNS, OUT_FILE))
for it, o in list(zip(sel, outs))[:3]:
    print('\n样例 ·', it['标题'][:46])
    print(json.dumps(o, ensure_ascii=False, indent=1)[:400])
