# -*- coding: utf-8 -*-
from pathlib import Path
import csv, json, datetime
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter

project=Path('/Users/bot1/Volumes/root_for_ai/AI工作区/通用_法务合同台账_合同存档_20260607_1036')
deliv=project/'deliverables'
docs=project/'docs'
base_csv=deliv/'合同总表_20260607.csv'
base_xlsx=deliv/'合同总表_20260607.xlsx'
md_path=deliv/'合同总表_20260607.md'
analysis_path=deliv/'新增法务文档记录_20260607_第21-28份_IP鲜活授权证书包.md'
explain_path=deliv/'合同梳理说明_20260607.md'
catalog_csv=deliv/'可发送文件目录_20260607.csv'
catalog_md=deliv/'可发送文件目录_20260607.md'
manifest_path=docs/'source_files_manifest.json'
ocr_summary_path=docs/'ocr_summary.json'

cols=['序号','归档编号','签约/生效时间（OCR）','合同名称','合同类型','项目/IP','甲方','乙方','其他方/合作方','合约有效期/授权期限','授权/合作范围','金额/费用/分成','争议解决','风险/待核对','原始文件','OCR文本','SHA256','页数','OCR字符数']

def read_csv_rows(path):
    if not path.exists(): return []
    with path.open('r', encoding='utf-8-sig', newline='') as f:
        return list(csv.DictReader(f))

def write_csv_rows(path, rows, fieldnames):
    with path.open('w', encoding='utf-8-sig', newline='') as f:
        w=csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')
        w.writeheader()
        for r in rows:
            w.writerow({c:r.get(c,'') for c in fieldnames})

rows=read_csv_rows(base_csv)
norm=[]
for r in rows:
    rr={c:r.get(c,'') for c in cols}
    try: seq=int(str(rr.get('序号','')).strip())
    except Exception: seq=None
    if seq in range(21,29):
        continue
    norm.append(rr)

manifest=json.loads(manifest_path.read_text(encoding='utf-8'))
manifest_by_seq={int(m['seq']):m for m in manifest}
ocr_list=json.loads(ocr_summary_path.read_text(encoding='utf-8')) if ocr_summary_path.exists() else []
ocr_by_file={o.get('pdf'):o for o in ocr_list}

def meta(seq):
    m=manifest_by_seq[seq]
    name=Path(m['stored_pdf']).name
    return m, ocr_by_file.get(name,{})

def mk(seq, archive_id, signed, name, project_ip, party_a, party_b, others, term, scope, risk, dispute='以对应授权合作协议/授权许可协议为准；证书未单独载明', money='无直接金额条款；以对应详细授权合同/授权合作协议为准'):
    m,o=meta(seq)
    return {
        '序号':str(seq), '归档编号':archive_id, '签约/生效时间（OCR）':signed, '合同名称':name,
        '合同类型':'授权书/IP授权凭证', '项目/IP':project_ip, '甲方':party_a, '乙方':party_b, '其他方/合作方':others,
        '合约有效期/授权期限':term, '授权/合作范围':scope, '金额/费用/分成':money, '争议解决':dispute,
        '风险/待核对':risk, '原始文件':m['stored_pdf'], 'OCR文本':o.get('ocr_text',f'work/extracted_text_ocr/{Path(m["stored_pdf"]).stem}.txt'),
        'SHA256':m['sha256'], '页数':str(m.get('pages',1)), 'OCR字符数':str(o.get('chars',''))
    }

new_rows=[
    mk(21,'LC-20260607-021','授权日期/签署页：2023-10（OCR只识别到年月，需人工复核）','授权证书（三星堆博物馆IP运营及授权顾问）','三星堆博物馆 / 三星堆IP','广汉市三星堆文旅发展有限公司（四川广汉三星堆博物馆授权的独家代理商，OCR识别）','杭州鲜活万物文化科技有限公司','四川广汉三星堆博物馆','2023-10-09至2024-10-08（截至2026-06-07已过期）','乙方为三星堆博物馆IP运营及授权顾问（非独家），开展对外IP授权相关对接洽谈中介工作；受双方《授权合作协议》约束','授权期限已过期；证书注明正本有效、不得影印/涂改/转让；如需对外发送或继续使用，需核验是否有续签/新证书及原件。'),
    mk(22,'LC-20260607-022','授权日期：2023-04-17（OCR识别）','授权证书（中国文字博物馆跨界联合业务运营服务商）','中国文字博物馆IP / 跨界联合','中国文字博物馆','杭州鲜活万物文化科技有限公司','第三方被授权合作方（可转授权/授权第三方，OCR识别）','2023-04-17至2027-12-31','乙方作为中国文字博物馆“跨界联合”业务运营服务商；可使用名称、商标、logo、图片、文字等资源进行跨界联名产品开发、活动宣传、展览、策划、数字藏品、节目改编等；可授权第三方开展跨界联合业务','仍在期限内；具体内容以《中国文字博物馆授权许可协议》为准，外发前需核验详细协议、授权单位盖章和第三方使用审批边界。'),
    mk(23,'LC-20260607-023','授权日期：2023-09-20附近（OCR识别不完整，需人工复核）','授权证书（熊大卫的元气食堂跨界联合业务运营服务商）','熊大卫的元气食堂IP','杭州思岩文化创意有限公司（OCR识别，需核对印章主体）','杭州鲜活万物文化科技有限公司','第三方被授权合作方（可转授权/授权第三方，OCR识别）','2023-09-20至2024-09-19（截至2026-06-07已过期）','乙方作为“熊大卫的元气食堂”跨界联合业务运营服务商；可使用名称、商标、图片、文字等资源进行跨界联名产品开发、活动宣传、展览、策划、数字藏品、节目改编等；可授权第三方','授权期限已过期；授权单位印章/主体 OCR 不清，外发或继续使用前需确认续签及原件。'),
    mk(24,'LC-20260607-024','授权日期：2023-09-20附近（OCR识别不完整，需人工复核）','授权证书（古龙跨界联合业务运营服务商）','古龙IP / 跨界联合','杭州萌果文化创意有限公司（OCR识别，需核对印章主体）','杭州鲜活万物文化科技有限公司','第三方被授权合作方（可转授权/授权第三方，OCR识别）','2023-09-20至2024-09-19（截至2026-06-07已过期）','乙方作为“古龙”跨界联合业务运营服务商；可使用名称、商标、图片、文字等资源进行跨界联名产品开发、活动宣传、展览、策划、数字藏品、节目改编等；可授权第三方','授权期限已过期；授权单位印章/主体 OCR 不清，外发或继续使用前需确认续签及原件。'),
    mk(25,'LC-20260607-025','授权日期：2020-04-21（OCR识别）','授权证书（敦煌美术研究所官方独家商业运营方）','敦煌美术研究所IP','敦煌美术研究所（OCR识别，盖章需人工复核）','杭州鲜活万物品牌管理有限公司','跨界联合合作第三方','2020-04-21至2030-04-20','乙方作为敦煌美术研究所官方独家商业运营方，代理商业运作全部关联业务；可使用所内藏品、名称及商标应用于跨界联名产品开发、联名活动宣传、展览策划等运营活动','仍在期限内；“官方独家商业运营方”权限较宽，外发/转授权前需核对完整授权链、盖章主体及是否另有品类/地域/审批限制。'),
    mk(26,'LC-20260607-026','授权日期：2023-10附近（OCR识别不完整，需人工复核）','授权证书（珠峰文旅IP官方独家运营商）','珠峰文旅IP','授权单位 OCR 不清，疑似珠峰文旅相关权利方（需人工复核）','杭州鲜活万物文化科技有限公司','可商业化合作/渠道运营/数字化运营等合作方','2023-10-01至2033-09-30','乙方为珠峰文旅IP官方独家运营商；可按《IP授权许可协议》对矢量图、高分辨率照片、设计元素、文字资料、音视频资料、名称、注册商标等进行商业化运作，包括产品开发、联合跨界、销售渠道运营、数字化运营开发等','仍在期限内；授权单位/盖章主体 OCR 不清，且权限依赖《IP授权许可协议》，外发前需复核原件和协议。'),
    mk(27,'LC-20260607-027','授权日期：2021-05-26（OCR识别）','授权证书（贵州省博物馆商标品牌运营服务商）','贵州省博物馆IP/商标品牌','贵州省博物馆','杭州鲜活万物品牌管理有限公司','第三方使用方（需贵州省博物馆审核通过）','2021-05-26至2024-05-25（截至2026-06-07已过期）','乙方为贵州省博物馆商标品牌合法运营服务商；可将贵州省博物馆名称、商标、图片、文字等资源自行或经贵州省博物馆审核通过后对第三方作合法使用','授权期限已过期；第三方使用需博物馆审核通过；外发或继续使用前需确认续期授权。'),
    mk(28,'LC-20260607-028','授权日期：2023-06-10（OCR识别）','授权证书（重庆中国三峡博物馆商标品牌合作运营服务商）','重庆中国三峡博物馆IP/商标品牌','重庆文博展览有限公司（重庆中国三峡博物馆）（OCR识别）','杭州鲜活万物文化科技有限公司','第三方使用方（使用方案需书面审核通过）','2023-06-10至2026-06-09（截至2026-06-07临近到期）','乙方为重庆中国三峡博物馆商标品牌合作运营服务商；可就商标、logo、图片、文字等资源使用与第三方洽谈；使用方案经重庆文博展览有限公司（重庆中国三峡博物馆）书面审核通过后第三方可以合法使用','临近到期（2026-06-09）；第三方使用需书面审核通过；如需继续招商/授权，应尽快确认续期或新证书。'),
]
all_rows=norm+new_rows
all_rows.sort(key=lambda r:int(str(r.get('序号','0')).strip() or 0))
write_csv_rows(base_csv, all_rows, cols)

src_cols=['序号','展示文件名','原始缓存文件名','存档PDF','原生文本','OCR文本','SHA256','页数','原生文本字符数','OCR字符数','添加时间','文件类型','来源压缩包']
src_rows=[]
for m in sorted(manifest, key=lambda x:int(x.get('seq',0))):
    name=Path(m.get('stored_pdf','')).name
    o=ocr_by_file.get(name,{})
    src_rows.append({'序号':m.get('seq'),'展示文件名':m.get('display_name'),'原始缓存文件名':m.get('original'),'存档PDF':m.get('stored_pdf'),'原生文本':m.get('stored_text'),'OCR文本':o.get('ocr_text',''),'SHA256':m.get('sha256'),'页数':m.get('pages'),'原生文本字符数':m.get('chars'),'OCR字符数':o.get('chars',''),'添加时间':m.get('added_at'),'文件类型':m.get('kind','pdf'),'来源压缩包':m.get('container_archive','')})

def category_for(r):
    name=(r.get('合同名称','')+' '+r.get('合同类型','')+' '+r.get('项目/IP','')).lower()
    if '授权证书' in name or '授权书' in name: return '授权书/IP授权凭证'
    if '保密' in name or 'nda' in name: return '保密协议/NDA'
    if '法律顾问' in name or '法务服务' in name: return '法务服务合同'
    if '采购' in name or '产品合作' in name or '文化产品' in name: return '产品合作/采购订单'
    if '屏销宝' in name or '电视淘宝' in name: return '广告投放/技术服务'
    if '品牌/ip授权代理' in name or '授权代理' in name: return '品牌/IP授权代理合同'
    return '合同/协议'

catalog_cols=['序号','归档编号','文件类别','可检索关键词','建议调用场景','展示文件名','存档PDF','合同名称','甲方','乙方','有效期/授权期','SHA256']
manifest_by_rel={m['stored_pdf']:m for m in manifest}
catalog=[]
for r in all_rows:
    m=manifest_by_rel.get(r.get('原始文件',''),{})
    cat=category_for(r)
    keywords='、'.join([x for x in [r.get('合同名称',''), r.get('项目/IP',''), r.get('甲方',''), r.get('乙方',''), cat, m.get('display_name','')] if x])
    if cat=='授权书/IP授权凭证':
        scene='对外证明IP/品牌授权、招商洽谈、跨界联名、查授权期限/范围时调用；外发前核对是否过期及是否需原件/书面审批'
    else:
        scene={
            '保密协议/NDA':'对外合作前保密约束、产品开发资料披露前、追责泄密时调用',
            '法务服务合同':'查询法务服务期限、服务额度、顾问服务范围、仲裁条款时调用',
            '产品合作/采购订单':'查供应商、采购金额、交付/结算、产品质量/IP责任时调用',
            '广告投放/技术服务':'查电视淘宝屏销宝年框投放、金额、曝光量、付款与风险时调用',
            '品牌/IP授权代理合同':'查IP授权/代理范围、期限、分成、招商合作时调用',
        }.get(cat,'需要调取该合同/协议原件或核对条款时调用')
    catalog.append({'序号':r.get('序号',''),'归档编号':r.get('归档编号',''),'文件类别':cat,'可检索关键词':keywords[:500],'建议调用场景':scene,'展示文件名':m.get('display_name',''),'存档PDF':r.get('原始文件',''),'合同名称':r.get('合同名称',''),'甲方':r.get('甲方',''),'乙方':r.get('乙方',''),'有效期/授权期':r.get('合约有效期/授权期限',''),'SHA256':r.get('SHA256','')})
write_csv_rows(catalog_csv, catalog, catalog_cols)

def write_md_table(path, rows, fieldnames, title, note=''):
    md=[f'# {title}\n']
    if note: md.append(f'> {note}\n')
    md.append('| ' + ' | '.join(fieldnames) + ' |')
    md.append('| ' + ' | '.join(['---']*len(fieldnames)) + ' |')
    def esc(x): return ('' if x is None else str(x)).replace('|','｜').replace('\n','<br>')
    for r in rows:
        md.append('| ' + ' | '.join(esc(r.get(c,'')) for c in fieldnames) + ' |')
    path.write_text('\n'.join(md)+'\n', encoding='utf-8')

write_md_table(md_path, all_rows, cols, '合同总表（截至2026-06-07）', '本表根据PDF/图片文本层及OCR整理；扫描件中的签署日期、印章、手写内容、金额/表格均需以原件人工复核为准。银行账号、电话、税号等敏感信息不在本表列示。')
write_md_table(catalog_md, catalog, catalog_cols, '可发送文件目录（截至2026-06-07）', '用于后续按关键词/类别快速找到原件并发送。若用户重复发送同一文件，按SHA256去重并提醒。授权书外发前需核对是否过期、是否需原件或书面审批。')

field_desc=[
    {'字段':'签约/生效时间（OCR）','说明':'根据PDF/图片文本层/OCR识别；签署页、盖章页、手写日期一律需人工复核。'},
    {'字段':'合约有效期/授权期限','说明':'记录合同期限、代理/授权期限、续约或终止规则；如合同仅约定生效条件则说明。'},
    {'字段':'金额/费用/分成','说明':'仅摘录主交易口径；银行账号、税号等敏感信息不进入总表。'},
    {'字段':'风险/待核对','说明':'法务辅助风险提示，不替代执业律师意见；高风险合同建议律师最终确认。'},
    {'字段':'SHA256','说明':'用于校验原始文件未被篡改或重复上传。'},
    {'字段':'可发送文件目录','说明':'按文件类别、关键词、调用场景维护，便于后续直接调取/发送原件。'},
]
now=datetime.datetime.now().isoformat(timespec='seconds')
process_rows=[
    {'时间':now,'事项':'追加处理第21-28份IP鲜活授权证书包','说明':'RAR包内8张授权证书图片已解压、按单张证书入账、OCR并分类为授权书/IP授权凭证。'},
    {'时间':now,'事项':'去重策略','说明':'按单个证书图片SHA256去重；本批8张未发现与既有文件重复。RAR压缩包也已留存于source_packages并记录包哈希。'},
    {'时间':now,'事项':'可发送目录','说明':'新增/更新可发送文件目录；后续可按IP名称（如中国文字博物馆、敦煌美术研究所、珠峰文旅等）直接调取对应证书图片。'},
    {'时间':now,'事项':'授权期限提醒','说明':'三星堆、元气食堂、古龙、贵州省博物馆授权已过期；三峡博物馆授权临近到期；外发/继续使用前建议复核续期。'},
]

def add_sheet(wb, title, rows, fieldnames):
    ws=wb.create_sheet(title)
    ws.append(fieldnames)
    for r in rows:
        ws.append([r.get(c,'') for c in fieldnames])
    return ws

wb=Workbook(); wb.remove(wb.active)
add_sheet(wb,'合同总表',all_rows,cols)
add_sheet(wb,'源文件清单',src_rows,src_cols)
add_sheet(wb,'可发送文件目录',catalog,catalog_cols)
add_sheet(wb,'字段说明',field_desc,['字段','说明'])
add_sheet(wb,'处理说明',process_rows,['时间','事项','说明'])
for ws in wb.worksheets:
    ws.freeze_panes='A2'
    for cell in ws[1]:
        cell.font=Font(bold=True, color='FFFFFF')
        cell.fill=PatternFill('solid', fgColor='4F81BD')
        cell.alignment=Alignment(horizontal='center', vertical='center', wrap_text=True)
    for row_cells in ws.iter_rows(min_row=2):
        for cell in row_cells:
            cell.alignment=Alignment(vertical='top', wrap_text=True)
    for col in range(1, ws.max_column+1):
        letter=get_column_letter(col)
        max_len=0
        for cell in ws[letter]:
            val='' if cell.value is None else str(cell.value)
            max_len=max(max_len, min(len(val), 60))
        ws.column_dimensions[letter].width=max(10, min(max_len+2, 45))
    ws.auto_filter.ref=ws.dimensions
wb.save(base_xlsx)

analysis=f'''# 新增法务文档记录（第21-28份｜IP×鲜活授权证书包）

处理时间：{now}

来源压缩包：`source_packages/21_IP_鲜活授权证书.rar`  
压缩包 SHA256：`314e433f795aeb6a423f3d79d53f51c4785896380e3de91cfcc1b31f332ba7bf`

> 说明：本批为 RAR 压缩包内 8 张授权证书图片，已按单张证书分别入账，便于后续按 IP 名称直接发送对应原件。OCR 结果用于检索和辅助判断；印章、授权单位、手写日期、是否原件有效需以图片原件人工复核为准。

## 本批归类与期限提醒

| 编号 | 文件/IP | 归类 | 授权期限（OCR） | 状态提醒（以 2026-06-07 为基准） |
|---|---|---|---|---|
| LC-20260607-021 | 三星堆博物馆 | 授权书/IP授权凭证 | 2023-10-09至2024-10-08 | 已过期，需确认续期/新证书 |
| LC-20260607-022 | 中国文字博物馆 | 授权书/IP授权凭证 | 2023-04-17至2027-12-31 | 仍在期限内，外发前核对详细授权许可协议 |
| LC-20260607-023 | 熊大卫的元气食堂 | 授权书/IP授权凭证 | 2023-09-20至2024-09-19 | 已过期，需确认续期/新证书 |
| LC-20260607-024 | 古龙 | 授权书/IP授权凭证 | 2023-09-20至2024-09-19 | 已过期，需确认续期/新证书 |
| LC-20260607-025 | 敦煌美术研究所 | 授权书/IP授权凭证 | 2020-04-21至2030-04-20 | 仍在期限内，权限较宽，需核完整授权链 |
| LC-20260607-026 | 珠峰文旅 | 授权书/IP授权凭证 | 2023-10-01至2033-09-30 | 仍在期限内，授权单位 OCR 不清需复核 |
| LC-20260607-027 | 贵州省博物馆 | 授权书/IP授权凭证 | 2021-05-26至2024-05-25 | 已过期，需确认续期/新证书 |
| LC-20260607-028 | 重庆中国三峡博物馆 | 授权书/IP授权凭证 | 2023-06-10至2026-06-09 | 临近到期，需尽快确认续期 |

## 去重结果

本批8张证书图片均为新增，未发现与既有归档文件SHA256重复。后续重复上传同一图片或同一PDF，将按SHA256提醒已有归档编号与路径，不重复入账。

## 发送/使用注意

1. 授权书属于对外证明文件，发送前建议先确认目标用途、授权是否仍有效、是否需要正本或最新续期文件。
2. 已过期证书不建议作为当前有效授权对外使用，只可作为历史授权记录或续期谈判参考。
3. 三峡博物馆证书到期日为 2026-06-09，距离当前日期很近，应优先核实续期。
4. 多份证书均写明“具体内容以详细版授权合同/协议为准”，实际授权品类、地域、转授权权限、审批流程仍需以主协议为准。
'''
analysis_path.write_text(analysis, encoding='utf-8')

marker='## 2026-06-07 追加处理：第21-28份IP×鲜活授权证书包'
old=explain_path.read_text(encoding='utf-8') if explain_path.exists() else '# 合同梳理说明\n'
append=f'''\n\n{marker}\n\n- 新增处理 RAR 压缩包 `IP×鲜活授权证书.rar`，解压得到8张授权证书图片，并按单张证书分别入账。\n- 已更新 `合同总表`、`源文件清单`、`可发送文件目录`；后续可按 IP 名称直接调取对应授权证书图片。\n- 本批8张证书未发现SHA256重复；RAR包已留存于 `source_packages/21_IP_鲜活授权证书.rar`。\n- 期限提醒：三星堆、元气食堂、古龙、贵州省博物馆已过期；重庆中国三峡博物馆临近到期；中国文字博物馆、敦煌美术研究所、珠峰文旅仍在OCR识别期限内。\n'''
if marker in old:
    old=old[:old.index(marker)].rstrip()+append
else:
    old=old.rstrip()+append
explain_path.write_text(old+'\n',encoding='utf-8')

readme=project/'README.md'
rt=readme.read_text(encoding='utf-8') if readme.exists() else '# 法务合同台账与合同存档\n'
line='- 当前台账：截至2026-06-07已整理28份合同/法务文档；含合同、授权书/授权证书、NDA、法务服务、产品合作/采购订单；可按“可发送文件目录”快速调取原件。'
if '- 当前台账：' in rt:
    rt='\n'.join([line if l.startswith('- 当前台账：') else l for l in rt.splitlines()])+'\n'
else:
    rt=rt.rstrip()+'\n'+line+'\n'
readme.write_text(rt,encoding='utf-8')

print('UPDATED_21_28')
print('rows', len(all_rows))
print('source_manifest_count', len(manifest))
print('ocr_summary_count', len(ocr_list))
print('xlsx', base_xlsx, base_xlsx.stat().st_size)
print('csv', base_csv, base_csv.stat().st_size)
print('catalog_csv', catalog_csv, catalog_csv.stat().st_size)
print('analysis', analysis_path, analysis_path.stat().st_size)
for r in all_rows[-8:]:
    print(r['序号'], r['归档编号'], r['项目/IP'], r['合约有效期/授权期限'])
