from pathlib import Path
from lxml import etree
import json,zipfile,hashlib,re,subprocess
OUT=Path(__file__).resolve().parent;ROOT=OUT.parents[1]
name='数字技术中心AI已落地应用与跨团队复制指南_20260926'
docx=OUT/'output'/f'{name}.docx';pdf=OUT/'output'/f'{name}.pdf'
template=ROOT/'standards-stack/agent-skills/skills/kangbite-word-template/assets/kangbite-digital-sports-word-template.docx'
W={'w':'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
checks=[]
with zipfile.ZipFile(template) as a,zipfile.ZipFile(docx) as b:
 for part in ['word/header1.xml','word/_rels/header1.xml.rels','word/media/image1.png']:
  assert a.read(part)==b.read(part),part
 checks.append('模板页眉XML、图片关系和Logo逐字节一致')
 for part in b.namelist():
  if part.endswith(('.xml','.rels')):etree.fromstring(b.read(part))
 for part,xpath in [('word/styles.xml','//w:style[@w:styleId="a5"]/w:pPr/w:pBdr'),('word/document.xml','//w:sectPr/w:pgSz'),('word/document.xml','//w:sectPr/w:pgMar')]:
  x=etree.fromstring(a.read(part)).xpath(xpath,namespaces=W)[-1]
  y=etree.fromstring(b.read(part)).xpath(xpath,namespaces=W)[-1]
  assert etree.tostring(x,method='c14n',exclusive=True)==etree.tostring(y,method='c14n',exclusive=True),xpath
 checks.append('A4页幅、页边距和Header下边框保持模板参数')
 body=etree.fromstring(b.read('word/document.xml'));text=''.join(body.xpath('//w:t/text()',namespaces=W))
 assert len(body.xpath('//w:sectPr',namespaces=W))==1
 assert len(body.xpath('//w:br[@w:type="page"]',namespaces=W))==8
 checks.append('正文为可编辑文字和表格；1节、8个明确分页，无模板空白节残留')
subprocess.run(['pdftotext','-raw',str(pdf),str(OUT/'output/rendered-text.txt')],check=True)
rendered=(OUT/'output/rendered-text.txt').read_text();pages=[s for s in rendered.split('\f') if s.strip()]
content=json.loads((OUT/'report-content.json').read_text());assert len(pages)==len(content)==9
norm=lambda s:re.sub(r'\s+','',s)
all_text=norm(rendered);all_doc=norm(text);missing=[]
for i,p in enumerate(content):
 assert norm(p['title']) in norm(pages[i]),(i,p['title'])
 assert '康比特数字体育科技' in norm(pages[i]),i
 for block in p['blocks']:
  strings=([*block[1],*[x for row in block[2] for x in row]] if block[0]=='table' else block[1:])
  for s in strings:
   if norm(s) not in all_doc or norm(s) not in all_text:missing.append(s[:50])
assert not missing,missing
checks.append('9页对应9章；全部正文和表格文字在DOCX与PDF回读中均存在；每页有中文页眉')
assert not re.search(r'\b(?:\d{1,3}\.){3}\d{1,3}\b|LTAI[\w]{12,}|BEGIN PRIVATE KEY',text)
checks.append('未包含主机地址、密钥或实际供应商报价')
manifest={p.name:{'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'storage':'local ignored output; internal circulation'} for p in [docx,pdf]}
(OUT/'asset-manifest.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2)+'\n')
result={'status':'pass','pages':9,'checks':checks,'visual_review':'9/9页已逐页查看；中文/品牌页眉/表格/分页无缺失或裁切','renderer':'LibreOfficeDev 26.8 + explicit task-local fontconfig; PDF via pdftoppm; not claiming WPS/Word desktop validation'}
(OUT/'validation.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n')
print(json.dumps(result,ensure_ascii=False,indent=2))
