#!/usr/bin/env python3
"""从交付版 PDF 反查全卷 50 件的起止页码，输出索引表回填用对照表。

原理：导出管线把每一件的一级标题另起页排在页首，页首文本形态固定为
      GHY-26691 / <卷序> / <页眉> / <件名>。
      卷序（如 2.2.21）本身不唯一——正文里的 4.2、3.2 这类小节号会撞上——
      所以按 vol_order/doc_order 的**既定件序顺序扫页**，只认「下一件」，
      并用件名二次确认。顺序扫描是这份映射唯一可靠的消歧手段。

用法：python3 pagemap.py [PDF 路径]      默认 ../_portal/build/投标文件.pdf
环境：PORTAL_DIR 可覆盖门户目录（取 portal.db 用）
依赖：pdftotext（poppler-utils）
"""
import re, sys, subprocess, sqlite3, os

HERE = os.path.dirname(os.path.abspath(__file__))
PORTAL = os.environ.get('PORTAL_DIR') or os.path.join(HERE, '..', '_portal')
pdf = sys.argv[1] if len(sys.argv) > 1 else os.path.join(PORTAL, 'build', '投标文件.pdf')

txt = subprocess.run(['pdftotext', pdf, '-'], capture_output=True).stdout.decode('utf8', 'ignore')
pn = re.compile(r'第\s*(\d+)\s*页\s*/\s*共\s*(\d+)\s*页')

c = sqlite3.connect(os.path.join(PORTAL, 'portal.db')); c.row_factory = sqlite3.Row
rows = c.execute("SELECT id,vol,title,vol_order,doc_order FROM tasks "
                 "WHERE in_bid=1 ORDER BY vol_order,doc_order").fetchall()
def seqof(r):
    d = r['doc_order']
    d = int(d) if float(d) == int(d) else d
    return f"{r['vol_order']}.{d}"
expect = [(seqof(r), r) for r in rows]

flat = lambda s: re.sub(r'\s+', '', s)
i, total, found = 0, None, []
for p in txt.split('\f'):
    m = pn.search(p)
    if not m: continue
    num, total = int(m.group(1)), int(m.group(2))
    if i >= len(expect): continue
    lines = [l.strip() for l in p.split('\n') if l.strip()]
    if len(lines) < 4 or not lines[0].startswith('GHY-26691'): continue
    seq, r = expect[i]
    # pdftotext 的行序随版面浮动——卷序有时在页眉前、有时在后，不能认死 lines[1]。
    # 只要页首 6 行里同时出现「下一件的卷序」和「下一件的件名」，就是这一件的首页。
    head = lines[1:7]
    if seq in head and flat(r['title'])[:10] in flat(''.join(head)):
        found.append((seq, r, num)); i += 1

print(f"# 全卷起止页对照（源 {os.path.basename(pdf)}，共 {total} 页，定位 {len(found)}/{len(expect)} 件）\n")
print("| 卷序 | 任务 | 组成件 | 起页 | 止页 | 页数 |")
print("|---|---|---|---|---|---|")
for n, (seq, r, p0) in enumerate(found):
    p1 = found[n+1][2] - 1 if n + 1 < len(found) else total
    print(f"| {seq} | `{r['id']}` | {r['title'][:36]} | {p0} | {p1} | {p1-p0+1} |")

miss = [(s, r['id']) for s, r in expect[len(found):]] if len(found) < len(expect) else []
missing = [(s, r['id']) for s, r in expect if s not in {f[0] for f in found}]
print()
if missing:
    print(f"⚠ 未定位 {len(missing)} 件，须人工核页：" + "、".join(f"{s}({t})" for s, t in missing))
    sys.exit(1)
print(f"✓ {len(expect)} 件全部定位，覆盖 p{found[0][2]}–p{total}")
