909eca8067
- QR (segno) in beelden/, met bijschrift Alle vier de reeksen - BRON wijst naar de overzichtspagina in plaats van een enkele repository - pptx en notitieversies opnieuw gebouwd Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
229 lines
18 KiB
Python
Vendored
229 lines
18 KiB
Python
Vendored
"""Bouwt de dia's van de workshop (22 stuks) als HTML-secties van 1920x1080 plus deck.json.
|
|
|
|
Standaard verwijzen de beelden naar de map beelden/ naast dit script, zodat bouw_pdf.py er een
|
|
PDF van kan maken. Met --artifact krijgen de beelden de paden van de online presentatie (asset store).
|
|
Licentie: CC BY-SA 4.0, zie ../../LICENSE.md. Logo van AI Netwerk Limburg uitgezonderd.
|
|
"""
|
|
import json, sys
|
|
from pathlib import Path
|
|
HERE = Path(__file__).resolve().parent
|
|
P = HERE / "project"; S = P / "slides"; S.mkdir(parents=True, exist_ok=True)
|
|
ARTIFACT = "--artifact" in sys.argv
|
|
# Uitgebreide begeleidersnotities (privé, niet in git): notities-begeleider.json naast dit script,
|
|
# per dia-id een tekst die de sprekersnotitie vervangt. Alleen gebruikt met --notities.
|
|
NOTITIES = {}
|
|
if "--notities" in sys.argv:
|
|
NOTITIES = json.loads((HERE / "notities-begeleider.json").read_text(encoding="utf-8"))
|
|
BEELD = {
|
|
"logo": ("../beelden/logo-ai-netwerk-limburg.png", "/_blob/120fdff17b59f8e1cc380d9086a1ad9f"),
|
|
"afgekeurd": ("../beelden/schoolnieuws-afgekeurd.jpg", "/_blob/9b09f7c211d54f2d5c5b5bdaa2b69fae"),
|
|
"flow": ("../beelden/studiecoach-flow.jpg", "/_blob/1c5f3486aa7ea647a8ee49269f3f95bf"),
|
|
"docent": ("../beelden/studiecoach-docent-antwoord.jpg", "/_blob/244acf99a4fa9215357a5e95e1eec2f4"),
|
|
"lex": ("../beelden/logo-lex-wit.png", "/_blob/a056bd447e5dc103fd01f7528f888bb9"),
|
|
"lexkleur": ("../beelden/logo-lex.png", "/_blob/042fe916c9a2622daf445ddb6d0bd222"),
|
|
"qr": ("../beelden/qr-git.png", "../beelden/qr-git.png"),
|
|
}
|
|
def beeld(naam): return BEELD[naam][1 if ARTIFACT else 0]
|
|
EIGENAAR = "Guido van Dijk, LeX Consultancy B.V. en AI Netwerk Limburg"
|
|
KOP = "Voor een teamvergadering of een bestuur · 20 minuten"
|
|
CONTACT = "guido@l-e-x.nl · guido@ai-netwerk-limburg.nl"
|
|
BRON = "git.lexconsultancy.nl/guido"
|
|
DARK="#1E2A3A"; LIGHT="#F7F4EC"; LIGHT2="#EFEAE0"; ACC="#D9480F"; BLUE="#2563EB"; BODY="#3D4756"; BODYD="#D9DEE6"; LINE="#D8D2C4"
|
|
H="'Rubik', Arial, sans-serif"; T="'Nunito Sans', Verdana, sans-serif"
|
|
LOGO=beeld("logo")
|
|
order=[]; sections={}
|
|
def slide(id_, body, bg=LIGHT, color=DARK, pad="128px 128px 160px", extra="display:flex; flex-direction:column; gap:40px", notes="", section=None, transition="fade", footer=True):
|
|
fcol = "#8A8F98" if bg in (LIGHT, LIGHT2) else "#9AA3B1"
|
|
notes = NOTITIES.get(id_, notes)
|
|
foot = f'<p style="position:absolute; left:128px; bottom:64px; font-size:24px; color:{fcol}">Wat is een AI-agent · LeX Consultancy · AI Netwerk Limburg · CC BY-SA 4.0</p>' if footer else ""
|
|
# Klein LeX-logo rechtsonder op elke dia; wit op donkere dia's, kleur op lichte.
|
|
lexlogo = beeld("lexkleur") if bg in (LIGHT, LIGHT2) else beeld("lex")
|
|
if footer: # cover en slot hebben het logo al bovenaan
|
|
foot += f'<img src="{lexlogo}" alt="LeX Consultancy" style="position:absolute; right:128px; bottom:52px; width:90px; height:52px; object-fit:contain">'
|
|
html = (f'<section id="{id_}" data-transition="{transition}" style="background:{bg}; color:{color}; font-family:{T}; padding:{pad}; {extra}">\n'
|
|
f'{body}\n{foot}\n<aside>{notes}</aside>\n</section>\n')
|
|
(S / f"{id_}.html").write_text(html, encoding="utf-8"); order.append(id_)
|
|
if section: sections[id_]={"description":section,"start":id_}
|
|
def h2(t): return f'<h2 style="font-family:{H}; font-size:64px; font-weight:600; line-height:1.1">{t}</h2>'
|
|
def eyebrow(t, c=ACC): return f'<p style="font-family:{H}; font-size:26px; font-weight:600; letter-spacing:2px; text-transform:uppercase; color:{c}">{t}</p>'
|
|
def p(t, size=32, c=BODY): return f'<p style="font-size:{size}px; color:{c}; line-height:1.4">{t}</p>'
|
|
def card(title, text, w="flex:1", bg="#FFFFFF", tc=DARK, bc=BODY, border=LINE):
|
|
return (f'<div style="{w}; display:flex; flex-direction:column; gap:16px; background:{bg}; padding:36px; border:1px solid {border}; border-radius:16px">'
|
|
f'<h3 style="font-family:{H}; font-size:34px; font-weight:600; color:{tc}; line-height:1.2">{title}</h3>'
|
|
f'<p style="font-size:27px; color:{bc}; line-height:1.4">{text}</p></div>')
|
|
def num(n, label, w="flex:1"):
|
|
return (f'<div style="{w}; display:flex; flex-direction:column; gap:8px"><p style="font-family:{H}; font-size:120px; font-weight:600; color:{ACC}; line-height:1">{n}</p>'
|
|
f'<p style="font-size:28px; color:{BODY}; line-height:1.3">{label}</p></div>')
|
|
def ul(items, size=30, c=BODY):
|
|
return f'<ul style="font-size:{size}px; color:{c}; line-height:1.5">' + "".join(f"<li>{i}</li>" for i in items) + "</ul>"
|
|
def table(rows, widths, size=27, head=True):
|
|
out=[f'<table style="font-size:{size}px; color:{BODY}; border:1px solid {LINE}; background:#FFFFFF">']
|
|
for i,r in enumerate(rows):
|
|
tag = "th" if (head and i==0) else "td"
|
|
bg = f' style="background:{LIGHT2}"' if (head and i==0) else ""
|
|
cells="".join(f'<{tag} style="width:{widths[j]}%; color:{DARK if tag=="th" else BODY}">{c}</{tag}>' for j,c in enumerate(r))
|
|
out.append(f"<tr{bg}>{cells}</tr>")
|
|
out.append("</table>"); return "".join(out)
|
|
def interactie(label): return f'<p style="font-family:{H}; font-size:26px; font-weight:600; letter-spacing:2px; text-transform:uppercase; color:{BLUE}">Interactie · {label}</p>'
|
|
|
|
|
|
# 1 cover
|
|
slide("cover", f'''
|
|
<img src="{LOGO}" alt="AI Netwerk Limburg" style="position:absolute; right:128px; top:96px; width:260px; height:210px; object-fit:contain">
|
|
<img src="{beeld("lex")}" alt="LeX Consultancy" style="position:absolute; right:148px; top:330px; width:150px; height:86px; object-fit:contain">
|
|
<div style="flex:1"></div>
|
|
{eyebrow(KOP, "#F4B183")}
|
|
<h1 style="font-family:{H}; font-size:112px; font-weight:600; line-height:1.05; color:{LIGHT}; width:1300px">Wat is een<br>AI-agent?</h1>
|
|
{p("En wat het niet is. Voor iedereen die er op school iets van moet vinden: docent, ondersteuning, communicatie, directie.", 34, BODYD)}
|
|
<div style="flex:1"></div>
|
|
{p("Guido van Dijk · LeX Consultancy B.V. · AI Netwerk Limburg", 28, "#9AA3B1")}
|
|
''', bg=DARK, color=LIGHT, footer=False, section="Wat is een agent, en wat niet",
|
|
notes="Twintig minuten, geen laptops nodig. Zeg vooraf: er komen drie momenten waarop ik iets aan jullie vraag.")
|
|
|
|
# 2 peiling
|
|
slide("peiling", interactie("2 minuten") + h2("Even peilen: handen omhoog") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{card("1", "Wie heeft deze week AI gebruikt voor werk? Een tekst, een samenvatting, een idee.")}
|
|
{card("2", "Wie weet zeker of dat mag, met het account dat hij daarvoor gebruikte?")}
|
|
{card("3", "Wie heeft ooit een prompt gedeeld met een collega die daarna niet werkte?")}
|
|
</div>
|
|
{p("De eerste hand gaat bijna altijd omhoog, de tweede bijna nooit. Daar gaat deze twintig minuten over: wat je bouwt, en waar je het bouwt.", 30)}
|
|
''', notes="Laat mensen echt de hand opsteken; het maakt de rest van de lezing een gesprek. Bij vraag 2 vaak stilte: benoem dat dat normaal is en dat het antwoord aan het eind komt.")
|
|
|
|
# 3 drie lagen
|
|
slide("lagen", h2("Drie lagen, en waar agents zitten") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{card("1 · Chatbot", "Je typt, je krijgt antwoord, elke keer opnieuw uitleggen. ChatGPT, Gemini, Copilot Chat, Claude.")}
|
|
{card("2 · Assistent in je pakket", "AI in de tools die je al hebt: Microsoft 365 Copilot, Gemini in Workspace. De school beslist via licenties.")}
|
|
{card("3 · Zelf iets bouwen", "Jij geeft een doel, regels en documenten, en deelt het met collega's. Dify, Copilot Studio, GPT's, Gems.", bg=DARK, tc=LIGHT, bc=BODYD, border=DARK)}
|
|
</div>
|
|
{p("Een agent hoort bij laag 3. Het verschil met laag 1 is niet het model: het is dat iemand vooraf heeft opgeschreven wat het ding moet doen, en wat niet.", 30)}
|
|
''', notes="Niet te lang bij de merken blijven hangen. De zin die telt is de onderste: het verschil zit in de instructie, niet in het model.")
|
|
|
|
# 4 wat is een agent
|
|
slide("watiseenagent", h2("Wat is een agent?") + f'''
|
|
{p("<b>Een agent krijgt een doel en bepaalt zelf welke stappen hij zet</b>, met de middelen die jij hem geeft. Een chatbot wacht op je volgende vraag; een agent werkt een opdracht af.", 32)}
|
|
<div style="display:flex; gap:32px">
|
|
{card("1 · Een doel en regels", "Wie ben je, wat doe je, en wat doe je nooit. Dat is de instructie: een half A4 gewone taal, geen code.")}
|
|
{card("2 · Middelen", "Kennis uit de documenten van de school, gereedschap om te zoeken, te rekenen of een bestand te maken, en geheugen van het gesprek.")}
|
|
{card("3 · Ruimte om te kiezen", "Hij bepaalt de volgorde, kijkt na elke stap wat eruit kwam, en probeert het opnieuw als het misgaat.")}
|
|
</div>
|
|
{p("Voorbeeld: een planner kreeg één opdracht, maak een studieplan voor de toetsweek. Niemand zei 'schrijf een programma'. Hij schreef er zelf een, voerde het uit, en gaf een Excel-bestand terug.", 28)}
|
|
''', notes="Zeg de eerste zin langzaam. Het voorbeeld onderaan is het moment waarop mensen het verschil met een chatbot voelen.")
|
|
|
|
# 5 wat een agent niet is
|
|
slide("nietagent", h2("Wat een agent niet is") + f'''
|
|
<div style="display:flex; gap:24px">
|
|
{card("Geen mens", "Geen oordeel, geen zorgplicht, geen verantwoordelijkheid. Een leerling die schrijft dat het thuis misgaat, hoort bij de mentor.")}
|
|
{card("Geen feitenmachine", "Hij weet niets, hij voorspelt tekst. Zonder documenten en zonder de regel 'zeg dat je het niet weet' verzint hij een antwoord dat er goed uitziet.")}
|
|
{card("Geen programma", "Hij doet niet twee keer precies hetzelfde. Moet iets zeker zijn, zoals een datum of een woordlimiet, dan doet code dat, of een mens.")}
|
|
{card("Geen beslisser", "Niet over toelating, cijfers of toezicht bij toetsen. Dat is hoog risico onder de AI Act.", bg=DARK, tc=LIGHT, bc=BODYD, border=DARK)}
|
|
</div>
|
|
{p("Een agent is geen collega en geen orakel. Het is gereedschap met een instructie, en iemand op school is er eigenaar van.", 30)}
|
|
''', section="Wat het niet is, en waar de mens hoort",
|
|
notes="De belangrijkste dia. Vraag na de tweede kaart: wie heeft wel eens een antwoord gekregen dat er goed uitzag maar niet klopte? Bijna alle handen gaan omhoog.")
|
|
|
|
# 6 drie voorbeelden
|
|
slide("voorbeelden", h2("Drie voorbeelden van school") + table([
|
|
["Agent","Wat erin gaat","Wat eruit komt","Wie heeft er wat aan"],
|
|
["Schoolgids-assistent","De schoolgids, en de vraag van een ouder","Een kort antwoord met het hoofdstuk erbij, of: dit staat er niet in, bel de receptie","Balie, mentoren, ouders"],
|
|
["Ouderbrief-assistent","Een wollige conceptbrief en de schrijfwijzer van de school","De herschreven brief, plus een lijstje wat er veranderd is en waarom","Mentoren, communicatie"],
|
|
["Studiecoach","De lesbrief van één thema en de studiewijzer","Uitleg, oefenvragen en hints; geen antwoorden op het huiswerk","Leerlingen, met een docent erachter"],
|
|
],[20,27,33,20], size=25) + p("Alle drie bestaan ze, in vier omgevingen, met handleidingen en video's erbij. Geen van drie is moeilijker dan een half A4 instructie schrijven.", 27),
|
|
notes="Vraag welke van de drie in dit team het snelst nut heeft. Meestal de ouderbrief. Dat is ook de makkelijkste om mee te beginnen.")
|
|
|
|
# 7 waar hoort de mens
|
|
slide("mens", interactie("3 minuten · in tweetallen") + h2("Waar hoort de mens?") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{num("1", "Welke fout mag bij ons nooit voorkomen? Dat is de eerste harde regel.")}
|
|
{num("2", "Welke beslissing laten we nooit aan een model? Daar hoort een mens.")}
|
|
{num("3", "Wie leest na, en wie is eigenaar als de bedenker weggaat?")}
|
|
</div>
|
|
{p("Bespreek het twee minuten met je buurman. Twee antwoorden op vraag 1 hardop; dat zijn meteen de regels waarmee je begint.", 30)}
|
|
''', notes="Dit is het hart van de lezing voor een team: niet de techniek maar de afspraak. Noteer de genoemde regels op een flap; die neemt het team mee.")
|
|
|
|
# 8 vier omgevingen
|
|
slide("vieromgevingen", h2("Dezelfde agent, vier omgevingen") + table([
|
|
["Wat je wilt","Dify","Copilot Chat","ChatGPT","Gemini"],
|
|
["Wie mag bouwen","iedereen met een account","elke collega met schoolaccount","alleen met betaald abonnement","elke collega met schoolaccount"],
|
|
["Kennis meegeven","kennisbank, meerdere documenten","in de instructie, 8.000 tekens","tot 20 kennisbestanden","bestanden, Drive, NotebookLM"],
|
|
["Bronvermelding","citaten","nee","Bronnen","label per antwoord"],
|
|
["Bestanden eruit","sandbox: xlsx, ics, md","Word en Excel op OneDrive","Word en Excel als download","Export naar Docs en Sheets"],
|
|
["Mens in de lus","ja","nee","nee","nee"],
|
|
["Kosten","credits of eigen sleutel","in de M365-licentie","per gebruiker per maand","in de Workspace-licentie"],
|
|
],[16,21,21,21,21], size=24) + p("<b>De instructie is het werk; de omgeving is de keuze.</b> Wat je op school al hebt, kan meer dan de meeste mensen denken.", 27),
|
|
section="Waar bouw je het, en wat mag",
|
|
notes="Niet voorlezen. Wijs twee rijen aan: kennis meegeven en mens in de lus. Zeg erbij dat van alle vier een reeks handleidingen bestaat met dezelfde zes agents.")
|
|
|
|
# 9 wat mag
|
|
slide("watmag", h2("Wat mag: vier vragen voor elke tool") + f'''
|
|
<div style="display:flex; gap:24px">
|
|
{card("Onder welk account?", "Privé, gratis, betaald, of school met verwerkersovereenkomst. Alleen in het laatste geval heeft de school iets te zeggen.")}
|
|
{card("Welke gegevens gaan erin?", "Geen persoonsgegevens van leerlingen, ouders of collega's. Ook geen ongepubliceerd toetsmateriaal.")}
|
|
{card("Wordt erop getraind?", "Consumentenaccounts vaak wel, zakelijke accounts en API-verkeer meestal niet. Controleer het, en laat het bevestigen.")}
|
|
{card("Wie beslist en kijkt na?", "Voor jezelf: jij. Voor leerlingen: een schoolbesluit, een toetsingskader, en iemand die de uitvoer leest.")}
|
|
</div>
|
|
{p("Copilot en Gemini zitten al in school; of ze aan mogen is een bestuursbesluit op basis van de DPIA's van SURF en SIVON. Dat is geen technische vraag.", 27)}
|
|
''', notes="Dit is het antwoord op vraag 2 van de peiling aan het begin. Verwijs naar het AI Toetsingskader Funderend Onderwijs van SIVON en Kennisnet.")
|
|
|
|
# 10 AVG en AI Act
|
|
slide("avgaiact", h2("AVG en AI Act, in het kort") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:14px; background:#FFFFFF; padding:32px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:34px; font-weight:600; color:{BLUE}">AVG</h3>
|
|
{ul(["De school is verantwoordelijke; de aanbieder is verwerker, meestal buiten de EU",
|
|
"Geen verwerkersovereenkomst betekent: geen persoonsgegevens erin",
|
|
"Zodra een leerling iets over zichzelf typt, is dat een verwerking waar de school over gaat"], 26)}
|
|
</div>
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:14px; background:#FFFFFF; padding:32px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:34px; font-weight:600; color:{ACC}">EU AI Act</h3>
|
|
{ul(["<b>Art. 4</b>: AI-geletterdheid verplicht voor wie AI inzet, sinds februari 2025",
|
|
"<b>Verboden</b>: emotieherkenning in het onderwijs",
|
|
"<b>Hoog risico</b>: toelating, beoordelen van leerresultaten, toezicht bij toetsen",
|
|
"<b>Art. 50</b>: een chatbot zegt dat hij een chatbot is"], 26)}
|
|
</div>
|
|
</div>
|
|
{p('Vuistregel: zodra iets beslist of vastlegt over een persoon, verandert de vraag van "werkt het?" in "mag het, en wie is verantwoordelijk?" Stand september 2026.', 26)}
|
|
''', notes="Twee zinnen per kant, geen juridische les. Als iemand doorvraagt: verwijs naar het toetsingskader en naar de functionaris gegevensbescherming van het bestuur.")
|
|
|
|
# 11 hoe begin je
|
|
slide("hoebegin", h2("Hoe je maandag begint") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{num("1", "Kies één document waar nu al steeds dezelfde vragen over komen. Zonder namen erin.")}
|
|
{num("2", "Schrijf een half A4: wie ben je, hoe antwoord je, en wat doe je als je het niet weet.")}
|
|
{num("3", "Test met drie vragen: twee gewone, en één waarop hij moet weigeren.")}
|
|
</div>
|
|
{p("Werkt het, deel het dan met twee collega's en spreek af wie het document bijhoudt. Werkt het niet, dan weet je binnen een kwartier waarom, en dat is ook winst.", 30)}
|
|
''', section="Hoe je begint",
|
|
notes="Maak het klein. De grootste fout is een groot plan; de tweede is een app zonder eigenaar. Bied aan om de handleidingen en video's te sturen.")
|
|
|
|
# 12 slot
|
|
slide("slot", f'''
|
|
<img src="{LOGO}" alt="AI Netwerk Limburg" style="position:absolute; right:128px; top:96px; width:220px; height:180px; object-fit:contain">
|
|
<img src="{beeld("lex")}" alt="LeX Consultancy" style="position:absolute; right:148px; top:296px; width:130px; height:74px; object-fit:contain">
|
|
<img src="{beeld("qr")}" alt="QR-code naar git.lexconsultancy.nl/guido" style="position:absolute; right:128px; top:404px; width:230px; height:230px; border-radius:12px">
|
|
<p style="position:absolute; right:128px; top:644px; width:230px; text-align:center; font-size:21px; color:#9AA3B1">Alle vier de reeksen</p>
|
|
<div style="flex:1"></div>
|
|
<h1 style="font-family:{H}; font-size:88px; font-weight:600; line-height:1.05; color:{LIGHT}; width:1400px">Gereedschap met een instructie.<br>En een eigenaar.</h1>
|
|
<div style="width:1330px; display:flex; flex-direction:column; gap:18px">
|
|
{p("Vier reeksen handleidingen en 28 uitlegvideo's: Dify, Microsoft Copilot, ChatGPT en Gemini. Dezelfde zes agents, stap voor stap, met screenshots.", 32, BODYD)}
|
|
{p("git.lexconsultancy.nl/guido", 30, "#F4B183")}
|
|
</div>
|
|
<div style="flex:1"></div>
|
|
{p("Alles op " + BRON + " · ai-netwerk-limburg.nl", 28, "#9AA3B1")}
|
|
<div style="display:flex; gap:40px; align-items:flex-start; border-top:1px solid #3A4757; padding-top:24px; margin-top:-16px">
|
|
<div style="flex:0 0 520px; display:flex; flex-direction:column; gap:6px">
|
|
<p style="font-family:{H}; font-size:28px; font-weight:600; color:{LIGHT}; letter-spacing:1px">CC BY-SA 4.0</p>
|
|
<p style="font-size:22px; color:#9AA3B1; line-height:1.4">Eigenaar en contact:<br>{EIGENAAR}<br>{CONTACT}</p>
|
|
</div>
|
|
{p("Deze presentatie mag je kopiëren, bewerken en hergebruiken, ook op je eigen school. Noem de bron (" + EIGENAAR + ", " + BRON + ") en deel je bewerking onder dezelfde licentie. Het logo van AI Netwerk Limburg valt buiten de licentie; namen van producten zijn merken van hun eigenaren.", 22, "#9AA3B1")}
|
|
</div>
|
|
''', bg=DARK, color=LIGHT, footer=False, notes="Sluit af met de vraag uit dia 7: welke regel nemen we mee? Noteer hem en stuur hem na.")
|
|
|
|
deck = {"v":4,"createdOnFiles":{"v":1,"at":"2026-09-22T14:00:00Z"},"title":"Wat is een AI-agent","order":order,"sections":sections,"cover":"cover",
|
|
"faces":{"rubik":{"family":"Rubik","href":"https://fonts.googleapis.com/css2?family=Rubik:wght@400;600&display=swap"},
|
|
"nunito-sans":{"family":"Nunito Sans","href":"https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;600;700&display=swap"}},
|
|
"designSystems":[]}
|
|
(P/"deck.json").write_text(json.dumps(deck, ensure_ascii=False, indent=1), encoding="utf-8")
|
|
print(len(order), "dia's:", order)
|