"""Check generated content and export contact sheets for visual review.""" from pathlib import Path from collections import Counter import json import re import unicodedata import argparse import pymupdf import openpyxl from PIL import Image, ImageDraw from pptx import Presentation ROOT=Path(__file__).resolve().parents[1] parser=argparse.ArgumentParser() parser.add_argument('--recalculated-workbook',type=Path,help='Optional independent LibreOffice/Excel recalculation copy') args=parser.parse_args() OUT=ROOT/'materials' PREV=OUT/'preview' PREV.mkdir(exist_ok=True) p=Presentation(OUT/'excel-ai-lecture.pptx') assert len(p.slides)==26 manifest=json.loads((OUT/'slides-manifest.json').read_text(encoding='utf-8')) assert sum(s['seconds'] for s in manifest)==3600 notes=(OUT/'speaker-notes.md').read_text(encoding='utf-8') durations=[int(v) for v in re.findall(r'\*\*Длительность:\*\* (\d+) секунд',notes)] assert durations==[s['seconds'] for s in manifest],durations assert all(s.notes_slide.notes_text_frame.text.strip() for s in p.slides) for n,s in enumerate(p.slides,1): for sh in s.shapes: assert sh.left>=0 and sh.top>=0,(n,sh.name,'negative position') assert sh.left+sh.width<=p.slide_width+10000,(n,sh.name,'too wide') assert sh.top+sh.height<=p.slide_height+10000,(n,sh.name,'too tall') book=openpyxl.load_workbook(OUT/'excel-practice.xlsx',data_only=True) rows=list(book['Реестр'].iter_rows(min_row=2,max_row=61,values_only=True)) assert len(rows)==60 and len({r[0] for r in rows})==60 assert all(all(v is not None for v in r[:6]) for r in rows) assert Counter(r[7] for r in rows)=={'Завершено':24,'Просрочено':18,'В срок':18} assert sum(r[5]=='Выполнено' and r[6]>r[4] for r in rows)==12 assert all(r[6] is None or r[6]<=book['Параметры']['B2'].value for r in rows) assert book['Проверка']['B6'].value==.5 assert len(book['Реестр'].tables)==1 assert len(book['Старт'].tables)==0 formulas=openpyxl.load_workbook(OUT/'excel-practice.xlsx',data_only=False) assert all(formulas['Реестр'].cell(r,8).data_type=='f' for r in range(2,62)) for r in rows: expected='Завершено' if r[5]=='Выполнено' else ('Просрочено' if r[4]page.rect.width+1 or y1>page.rect.height+1: warnings.append(f'Slide {i+1}: text exceeds page: {span["text"]}') page.get_pixmap(matrix=pymupdf.Matrix(1.4,1.4)).save(PREV/f'slide-{i+1:02}.png') for start in range(0,26,8): ids=list(range(start,min(start+8,26))) sheet=Image.new('RGB',(1200,360*((len(ids)+1)//2)),'#dedee8') for k,i in enumerate(ids): pm=pdf[i].get_pixmap(matrix=pymupdf.Matrix(.625,.625)) im=Image.frombytes('RGB',(pm.width,pm.height),pm.samples) x=(k%2)*600;y=(k//2)*360 sheet.paste(im,(x,y)) ImageDraw.Draw(sheet).text((x+12,y+339),f'SLIDE {i+1:02}',fill='black') sheet.save(PREV/f'contact-{start//8+1}.png') try: import zxingcpp result=zxingcpp.read_barcode(Image.open(OUT/'telegram-qr.png')) assert result and result.text=='https://t.me/kirill_bouko_it' # Verify QR survives PDF export, not only its standalone source image. result=zxingcpp.read_barcode(Image.open(PREV/'slide-25.png')) assert result and result.text=='https://t.me/kirill_bouko_it' qr_status='decoded from PNG and PDF slide 25' except ImportError: qr_status='decoder unavailable; image generated from exact URL' report={ 'slides':26,'total_seconds':3600,'notes_embedded':True,'pdf_pages':len(pdf), 'synthetic_rows':60,'completed':24,'open':36,'overdue_open':18,'overdue_share_open':.5, 'qr':qr_status,'render_text_warnings':warnings, 'recalculated_workbook':recalc_status, } (OUT/'validation.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n',encoding='utf-8') print(json.dumps(report,ensure_ascii=False,indent=2)) assert not warnings,'Inspect PDF text warnings before delivering'