Презентация и учебные материалы: Excel и ИИ-агенты

This commit is contained in:
apuc committed 2026-10-15 18:26:30 +03:00
1 parent 14ed9c15d8
commit 401b02b680
13 files changed
+1582

No files matched your search

+113
View File
@@ -0,0 +1,113 @@
"""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]<book['Параметры']['B2'].value else 'В срок')
assert expected==r[7]
assert all(book['Тесты'].cell(n,4).value==book['Тесты'].cell(n,5).value for n in range(2,6))
recalc_status='not performed in this check'
if args.recalculated_workbook:
recalculated=openpyxl.load_workbook(args.recalculated_workbook,data_only=True)
assert list(recalculated['Реестр'].iter_rows(min_row=2,max_row=61,values_only=True))==rows
for n in range(2,10):
assert recalculated['Проверка'].cell(n,2).value==book['Проверка'].cell(n,2).value
assert all(recalculated['Тесты'].cell(n,4).value==recalculated['Тесты'].cell(n,5).value for n in range(2,6))
recalc_status='independent recalculation matches all 60 rows, check totals and four boundary tests'
pdf=pymupdf.open(OUT/'excel-ai-lecture.pdf')
assert len(pdf)==26
def norm(t):
return ''.join(c for c in unicodedata.normalize('NFKC',t).lower() if c.isalnum())
warnings=[]
for i,s in enumerate(p.slides):
page=pdf[i]
pdf_text=norm(page.get_text())
for sh in s.shapes:
if not sh.has_text_frame:continue
for para in sh.text_frame.paragraphs:
expected=norm(para.text)
if expected and expected not in pdf_text:
warnings.append(f'Slide {i+1}: text not found intact: {para.text}')
for block in page.get_text('dict')['blocks']:
if 'lines' not in block:continue
for line in block['lines']:
for span in line['spans']:
x0,y0,x1,y1=span['bbox']
if min(x0,y0)<-1 or x1>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'