#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""省基金 PDF 解析 v4：锚点行 + 窗口重建（解决类型/名称/编号跨行）
校验：浙江 2667 项；广东 3171 项 / 42110 万元
"""
import csv
import io
import os
import re
from collections import defaultdict

import fitz

BASE = '/Users/liuxinyuan/Desktop/Hermes输出-工作类/销售分析/基金源文件'
ZJ = BASE + '/浙江_2026省自然科学基金拟资助项目清单.pdf'
GD = BASE + '/广东_2026省自然科学基金拟立项及资金安排表_转载件.pdf'
UNIT_KW = re.compile(r'(大学|学院|医院|研究院|研究所|中心|公司|实验室|疾控|科学院|学校|集团)')
CAT_KW = re.compile(r'(青基|探索|重点|杰青|重大|青稞|联合基金|地区科学|延续|面上|青年|杰出|优秀)')


def lines(page, tol=2.5):
    rows = defaultdict(list)
    for w in page.get_text('words'):
        rows[round(w[1] / tol)].append((w[0], w[1], w[4]))
    out = []
    for k in sorted(rows):
        it = sorted(rows[k], key=lambda z: z[0])
        ys = sorted(z[1] for z in it)
        out.append((ys[len(ys) // 2], [(x, t) for x, _, t in it]))
    return out


def band(it, lo, hi):
    return ''.join(t for x, t in it if lo <= x < hi).strip()


def build(pdf, x_name, x_unit, x_pi, x_amt, x_type, is_zj):
    doc = fitz.open(pdf)
    flat = []
    for pno in range(doc.page_count):
        for y, it in lines(doc[pno]):
            flat.append({
                'name': band(it, *x_name), 'unit': band(it, *x_unit),
                'pi': band(it, *x_pi),
                'amt': band(it, *x_amt), 'type': band(it, *x_type),
                'code': band(it, 285, 350) if is_zj else '',
                'no': band(it, 0, 70) if is_zj else '',
            })
    # 锚点：有单位 + 负责人 + 金额（广东）/ 有单位+负责人+类别（浙江）
    anchors = []
    for i, r in enumerate(flat):
        if is_zj:
            ok = bool(r['unit']) and bool(re.fullmatch(r'[\u4e00-\u9fff·]{2,8}', r['pi'] or '')) and bool(CAT_KW.search(r['type']))
        else:
            ok = bool(r['unit']) and bool(UNIT_KW.search(r['unit'])) and bool(re.fullmatch(r'[\u4e00-\u9fff·]{2,4}', r['pi'] or '')) and bool(re.fullmatch(r'\d+', r['amt'] or ''))
        if ok:
            anchors.append(i)
    rows, seen = [], set()
    for k, i in enumerate(anchors):
        lo = anchors[k - 1] + 1 if k else 0
        hi = anchors[k + 1] if k + 1 < len(anchors) else len(flat)
        win = flat[lo:hi + 1]
        name = ''.join(w['name'] for w in win if w['name'] and not re.fullmatch(r'\d+', w['name']))
        typ = ''.join(w['type'] for w in win if w['type'])
        cat = re.sub(r'[^\u4e00-\u9fff]', '', typ)
        code = ''.join(w['code'] for w in win if w['code'])
        no = next((w['no'] for w in win if re.fullmatch(r'\d{1,4}', w['no'] or '')), '')
        r = {'no': no, 'name': name, 'code': code, 'pi': flat[i]['pi'],
             'unit': flat[i]['unit'], 'amt': flat[i]['amt'], 'cat': cat}
        key = (r['unit'], r['pi'], r['name'][:12], r['amt'])
        if key in seen:
            continue
        seen.add(key)
        rows.append(r)
    return rows


zj = build(ZJ, (85, 290), (380, 445), (348, 380), (900, 901), (445, 999), True)
gd = build(GD, (100, 320), (320, 420), (420, 470), (470, 510), (545, 999), False)

print('█ 浙江行数:', len(zj), '（校验 2667）')
print('   类别:', {k: sum(1 for r in zj if k in r['cat']) for k in ('重大', '杰青', '重点', '探索', '青基', '联合', '青稞')})
print('█ 广东行数:', len(gd), '（校验 3171）')
print('   金额合计: %d 万元（校验 42110）' % sum(int(r['amt']) for r in gd if r['amt'].isdigit()))
print('   类型:', {k: sum(1 for r in gd if k in r['cat']) for k in ('面上', '青年', '重点')})

os.makedirs(BASE + '/parsed', exist_ok=True)
with io.open(BASE + '/parsed/浙江_2026省基金_明细.csv', 'w', encoding='utf-8-sig', newline='') as f:
    w = csv.DictWriter(f, fieldnames=['no', 'name', 'code', 'pi', 'unit', 'cat'])
    w.writeheader(); w.writerows(zj)
with io.open(BASE + '/parsed/广东_2026省基金_明细.csv', 'w', encoding='utf-8-sig', newline='') as f:
    w = csv.DictWriter(f, fieldnames=['no', 'name', 'unit', 'pi', 'amt', 'cat'])
    w.writeheader(); w.writerows(gd)
print('✅ CSV 写入 parsed/')
