ktf-ai-test / app.py
LbejchJakub's picture
Update app.py
a2080a4 verified
raw
history blame
22.6 kB
import gradio as gr
import json
import csv
from datetime import datetime
import os
import hashlib
# ADMIN HESLO - Změňte toto!
ADMIN_PASSWORD_HASH = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # "password" - ZMĚŇTE!
# Pro vytvoření vlastního hashe: import hashlib; hashlib.sha256("vase_heslo".encode()).hexdigest()
# Data úložiště
RESULTS_FILE = "vysledky_testu.csv"
FEEDBACK_FILE = "zpetna_vazba.csv"
# Inicializace souborů
if not os.path.exists(RESULTS_FILE):
with open(RESULTS_FILE, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['timestamp', 'kod_ucastnika', 'typ_testu', 'skore', 'maximalni_skore', 'procenta', 'odpovedi'])
if not os.path.exists(FEEDBACK_FILE):
with open(FEEDBACK_FILE, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['timestamp', 'kod_ucastnika', 'typ_testu', 'obtiznost', 'srozumitelnost', 'uzitecnost', 'komentar'])
def check_admin_password(password):
"""Ověří admin heslo"""
return hashlib.sha256(password.encode()).hexdigest() == ADMIN_PASSWORD_HASH
# Definice testů (bez správných odpovědí viditelných v UI)
TESTS = {
"STAFF": {
"nazev": "Test pro administrativní pracovníky (STAFF)",
"popis": "10 otázek zaměřených na praktické použití AI v kanceláři",
"otazky": [
{
"q": "Jaká je doporučená struktura efektivního promptu pro práci s AI?",
"opts": [
"A) Role + Kontext + Úkol + Formát",
"B) Pouze úkol, ostatní AI domyslí sama",
"C) Poskytnutí jasné role AI zlepšuje kvalitu odpovědí",
"D) Formát výstupu není důležitý",
"E) Kontext pomáhá AI lépe pochopit situaci"
],
"spravne": ["A", "C", "E"]
},
{
"q": "Co přesně znamená termín 'zero-shot prompting'?",
"opts": [
"A) Poskytnutí jednoho vzorového příkladu",
"B) Zadání instrukce bez jakýchkoliv příkladů",
"C) Poskytnutí tří vzorových příkladů",
"D) Nejjednodušší forma práce s AI",
"E) Vždy poskytuje nejlepší výsledky"
],
"spravne": ["B", "D"]
},
{
"q": "Co je 'answer pressure testing' a k čemu slouží?",
"opts": [
"A) Technika pro zrychlení generování odpovědí",
"B) Žádost, aby AI zkontrolovala a zdůvodnila své odpovědi",
"C) Příklad: 'Jsi si tímto výsledkem jistý? Zkontroluj svoje úvahy.'",
"D) Pomáhá odhalit případné halucinace AI",
"E) Snižuje kvalitu odpovědí tím, že AI zmate"
],
"spravne": ["B", "C", "D"]
},
{
"q": "Které z následujících úkolů lze bezpečně řešit pomocí AI v kancelářské práci?",
"opts": [
"A) Shrnutí dlouhých dokumentů do kratší formy",
"B) Extrakce konkrétních informací (jména, data, částky) z dokumentů",
"C) Anonymizace osobních údajů v dokumentech",
"D) Fyzická archivace papírových dokumentů",
"E) Překlad textů a změna stylu komunikace"
],
"spravne": ["A", "B", "C", "E"]
},
{
"q": "Co přesně jsou 'halucinace' v kontextu AI systémů?",
"opts": [
"A) AI vymýšlí informace, které vypadají věrohodně, ale jsou nepravdivé",
"B) Halucinace jsou vždy snadno rozpoznatelné",
"C) Je nezbytné vždy ověřovat důležitá fakta z jiných zdrojů",
"D) Answer pressure testing může pomoci odhalit halucinace",
"E) Nové modely AI už halucinace nemají"
],
"spravne": ["A", "C", "D"]
},
{
"q": "Co je 'token' a proč je tento pojem důležitý?",
"opts": [
"A) Token odpovídá přibližně 4 znakům textu",
"B) Token je základní jednotka zpracování textu v AI",
"C) Token je měna pro placení AI služeb",
"D) AI modely mají limit na počet tokenů",
"E) Token je totéž co jedno slovo"
],
"spravne": ["A", "B", "D"]
},
{
"q": "Jak správně pracovat s AI při respektování GDPR?",
"opts": [
"A) Anonymizovat všechny osobní údaje před nahráním do AI",
"B) GDPR se na práci s AI nevztahuje",
"C) Nikdy neposílat citlivá data do veřejných AI systémů",
"D) Znát politiku zpracování dat AI platformy",
"E) Osobní údaje lze do AI nahrávat bez omezení"
],
"spravne": ["A", "C", "D"]
},
{
"q": "Jak správně pracovat s češtinou vs. angličtinou při použití AI?",
"opts": [
"A) Pro češtinu explicitně uvést 'v češtině'",
"B) AI automaticky rozpozná jazyk vždy správně",
"C) Pro vědu preferovat angličtinu",
"D) Kvalita je stejná pro všechny jazyky",
"E) Lze kombinovat jazyky podle potřeby"
],
"spravne": ["A", "C", "E"]
},
{
"q": "Co znamená 'chain-of-thought prompting' a kdy ho použít?",
"opts": [
"A) Žádost o postupné vysvětlení kroků",
"B) Nejrychlejší způsob generování",
"C) Užitečné pro komplexní analýzy",
"D) Příklad: 'Analyzuj: 1) témata 2) problémy 3) řešení'",
"E) Nevhodné pro analytické úlohy"
],
"spravne": ["A", "C", "D"]
},
{
"q": "Co je 'role-playing' v kontextu práce s AI?",
"opts": [
"A) AI přiřadíte konkrétní roli",
"B) Příklad: 'Jsi zkušený HR manažer s 20 lety praxe'",
"C) Role nijak neovlivňuje odpovědi",
"D) Role pomáhá přizpůsobit jazyk a odbornost",
"E) Používá se pouze pro zábavu"
],
"spravne": ["A", "B", "D"]
}
]
},
"ACADEMIC": {
"nazev": "Test pro akademické pracovníky (ACADEMIC)",
"popis": "12 otázek: 6 základních + 6 metodologických",
"otazky": [
{
"q": "Jaká je struktura akademického promptu?",
"opts": [
"A) Role + Kontext + Úkol + Formát + Omezení",
"B) Pouze úkol",
"C) Požadavek na rozlišení fakta/interpretace",
"D) Žádost o označení míry jistoty",
"E) Formát není důležitý"
],
"spravne": ["A", "C", "D"],
"body": 1
},
{
"q": "Co je 'answer pressure testing' a proč je v akademii kritický?",
"opts": [
"A) Technika systematického zpochybňování",
"B) Příklad: 'Jsi si jistý? Odkud to? Alternativy?'",
"C) Pomáhá odhalit halucinace",
"D) V akademii méně důležitý než v kanceláři",
"E) Nutí AI přiznat nejistotu"
],
"spravne": ["A", "B", "C", "E"],
"body": 1
},
{
"q": "Proč jsou halucinace v akademii zvlášť problematické?",
"opts": [
"A) Jedna vymyšlená citace diskredituje celou práci",
"B) V nových modelech již vyřešeny",
"C) AI prezentuje vymyšlené info přesvědčivě",
"D) Týkají se především citací a faktů",
"E) Jsou vždy snadno rozpoznatelné"
],
"spravne": ["A", "C", "D"],
"body": 1
},
{
"q": "Rozdíl mezi 'asistencí' a 'nahrazením úsudku'?",
"opts": [
"A) Asistence = AI strukturuje MOJE myšlenky",
"B) Nahrazení = AI vytváří závěry které přebírám",
"C) V akademii irelevantní",
"D) Asistence = finální odpovědnost na mně",
"E) Oba postupy stejně přijatelné"
],
"spravne": ["A", "B", "D"],
"body": 1
},
{
"q": "Jak správně pracovat s AI a GDPR v akademickém výzkumu?",
"opts": [
"A) Anonymizovat data před nahráním",
"B) Data z výzkumu lze nahrávat bez omezení",
"C) Necitlivá data respondentů do veřejných AI",
"D) Enterprise AI nabízí lepší ochranu",
"E) GDPR se na akademii nevztahuje"
],
"spravne": ["A", "C", "D"],
"body": 1
},
{
"q": "Proč je pro akademii doporučena angličtina?",
"opts": [
"A) AI trénovány primárně na anglických akademických textech",
"B) Kvalita v češtině a angličtině je stejná",
"C) Pro vědu přesnější odpovědi v angličtině",
"D) Pro admin práci lepší čeština",
"E) Angličtina není důležitá"
],
"spravne": ["A", "C", "D"],
"body": 1
},
{
"q": "Které použití AI je přijatelné a které ne?",
"opts": [
"A) PŘIJATELNÉ: Návrh struktury mých myšlenek",
"B) PŘIJATELNÉ: Generování závěrů výzkumu",
"C) PŘIJATELNÉ: Identifikace slabých míst argumentace",
"D) NEPŘIJATELNÉ: Auto-psaní sekcí bez kontroly",
"E) PŘIJATELNÉ: Delegování hodnotových soudů"
],
"spravne": ["A", "C", "D"],
"body": 1.5
},
{
"q": "Jak pracovat s epistemickou nejistotou?",
"opts": [
"A) Požadovat explicitní míru jistoty",
"B) Důvěřovat autoritativnímu tónu AI",
"C) Ověřovat tvrzení z primárních zdrojů",
"D) Žádat alternativní interpretace",
"E) AI jako konečný arbitr"
],
"spravne": ["A", "C", "D"],
"body": 1.5
},
{
"q": "Jak správně pracovat se zdroji a citacemi?",
"opts": [
"A) AI často vymýšlí věrohodné citace",
"B) Každou citaci ověřit v databázích",
"C) Answer pressure: 'Odkud? Je DOI platné?'",
"D) Citace z AI spolehlivé pro publikování",
"E) Preferovat: PDF → shrnutí, ne 'cituj studie'"
],
"spravne": ["A", "B", "C", "E"],
"body": 1.5
},
{
"q": "Co musí akademik uvést ohledně použití AI?",
"opts": [
"A) Neuvádí se",
"B) Transparentně uvést v metodologii",
"C) AI je nástroj, ne autor - ale uvést použití",
"D) Citovat AI podle standardů (APA)",
"E) Závisí na pravidlech instituce"
],
"spravne": ["B", "C", "D", "E"],
"body": 1.5
},
{
"q": "Proč AI nemá 'skutečné porozumění'?",
"opts": [
"A) AI je statistický predikční model",
"B) Předpovídá pokračování textu z vzorců",
"C) Když neví, stejně něco vygeneruje",
"D) Nové modely už mají lidské porozumění",
"E) Proto nutná epistemická opatrnost"
],
"spravne": ["A", "B", "C", "E"],
"body": 1.5
},
{
"q": "Jaká je role AI v akademickém výzkumu?",
"opts": [
"A) Pomocný nástroj pro strukturování",
"B) Náhrada za odborný úsudek",
"C) Kritický partner pro zpochybňování",
"D) Autoritativní zdroj pravdy",
"E) Nástroj pro podporu, ne nahrazení"
],
"spravne": ["A", "C", "E"],
"body": 1.5
}
]
}
}
def vyhodnotit_test(typ_testu, odpovedi):
"""Vyhodnotí odpovědi a vrátí skóre"""
test = TESTS[typ_testu]
spravne = 0
celkove_body = 0
for i, otazka in enumerate(test["otazky"]):
body_za_otazku = otazka.get("body", 1)
uzivatel_odpovedi = set(odpovedi.get(f"q_{i}", []))
spravne_odpovedi = set(otazka["spravne"])
if uzivatel_odpovedi == spravne_odpovedi:
spravne += body_za_otazku
celkove_body += body_za_otazku
return spravne, celkove_body
def ulozit_vysledek(kod, typ, skore, max_skore, odpovedi):
"""Uloží výsledek do CSV"""
with open(RESULTS_FILE, 'a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([
datetime.now().isoformat(),
kod,
typ,
skore,
max_skore,
f"{(skore/max_skore*100):.1f}%",
json.dumps(odpovedi, ensure_ascii=False)
])
def ulozit_feedback(kod, typ, obtiznost, srozumitelnost, uzitecnost, komentar):
"""Uloží zpětnou vazbu"""
with open(FEEDBACK_FILE, 'a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([
datetime.now().isoformat(),
kod,
typ,
obtiznost,
srozumitelnost,
uzitecnost,
komentar
])
def get_results_csv(password):
"""Vrátí CSV s výsledky - pouze pro admina"""
if not check_admin_password(password):
return "❌ Nesprávné heslo"
if not os.path.exists(RESULTS_FILE):
return "📭 Zatím žádné výsledky"
with open(RESULTS_FILE, 'r', encoding='utf-8') as f:
return f.read()
def get_feedback_csv(password):
"""Vrátí CSV se zpětnou vazbou - pouze pro admina"""
if not check_admin_password(password):
return "❌ Nesprávné heslo"
if not os.path.exists(FEEDBACK_FILE):
return "📭 Zatím žádná zpětná vazba"
with open(FEEDBACK_FILE, 'r', encoding='utf-8') as f:
return f.read()
# Gradio UI
with gr.Blocks(title="Test AI znalostí KTF UK") as demo:
gr.Markdown("# 🎓 Test znalostí: Generativní AI")
gr.Markdown("**Katolická teologická fakulta UK, Praha 2025**")
user_kod = gr.State("")
user_typ = gr.State("")
user_odpovedi = gr.State({})
with gr.Tab("🔐 Začátek"):
gr.Markdown("### Vítejte v testu znalostí generativní AI")
gr.Markdown("Pro zahájení testu zadejte váš unikátní kód a vyberte typ testu.")
kod_input = gr.Textbox(
label="Váš unikátní kód",
placeholder="Např: KTF2025-001",
info="Tento kód obdržíte od organizátora"
)
typ_radio = gr.Radio(
choices=[
("STAFF - Pro administrativní pracovníky (10 otázek)", "STAFF"),
("ACADEMIC - Pro výzkumníky (12 otázek)", "ACADEMIC")
],
label="Typ testu",
info="Vyberte test odpovídající vaší roli"
)
start_btn = gr.Button("Začít test", variant="primary", size="lg")
start_msg = gr.Markdown("")
with gr.Tab("📝 Test") as test_tab:
test_info = gr.Markdown("")
otazky_ui = []
for i in range(12):
with gr.Group(visible=False) as q_group:
q_text = gr.Markdown("")
q_check = gr.CheckboxGroup(label="Vyberte VŠECHNY správné odpovědi")
otazky_ui.append((q_group, q_text, q_check))
submit_btn = gr.Button("Odeslat test", variant="primary", size="lg", visible=False)
with gr.Tab("📊 Výsledky") as results_tab:
result_md = gr.Markdown("")
gr.Markdown("### 💬 Zpětná vazba")
gr.Markdown("Pomozte mi prosím zlepšit kurz:")
obtiznost = gr.Radio(
choices=["Příliš snadný", "Přiměřený", "Příliš těžký"],
label="Obtížnost testu"
)
srozumitelnost = gr.Radio(
choices=["Výborná", "Dobrá", "Špatná"],
label="Srozumitelnost prezentací"
)
uzitecnost = gr.Radio(
choices=["Velmi užitečné", "Užitečné", "Málo užitečné"],
label="Užitečnost krzu pro praxi"
)
komentar = gr.Textbox(
label="Váš komentář ke kurzu (nepovinné)",
placeholder="Sdílejte své postřehy...",
lines=3
)
feedback_btn = gr.Button("Odeslat zpětnou vazbu", variant="secondary")
feedback_msg = gr.Markdown("")
with gr.Tab("👨‍💼 Admin") as admin_tab:
gr.Markdown("### 🔒 Administrátorský přístup")
gr.Markdown("Pouze pro organizátory testu")
admin_password = gr.Textbox(
label="Admin heslo",
type="password",
placeholder="Zadejte heslo"
)
with gr.Row():
results_btn = gr.Button("Stáhnout výsledky", variant="secondary")
feedback_download_btn = gr.Button("Stáhnout zpětnou vazbu", variant="secondary")
admin_output = gr.Textbox(
label="Data",
lines=20,
max_lines=30
)
gr.Markdown("**ℹ️ Poznámka:** Data můžete zkopírovat a uložit jako CSV soubor.")
# Logika
def start_test(kod, typ):
if not kod or not typ:
return {
start_msg: "❌ Vyplňte prosím kód i typ testu",
user_kod: "",
user_typ: ""
}
test = TESTS[typ]
num_q = len(test["otazky"])
updates = {
start_msg: f"✅ Test zahájen! Přejděte na záložku 'Test'",
user_kod: kod,
user_typ: typ,
test_info: f"## {test['nazev']}\n\n{test['popis']}\n\n**Počet otázek:** {num_q}"
}
for i in range(12):
if i < num_q:
q = test["otazky"][i]
updates[otazky_ui[i][0]] = gr.update(visible=True)
updates[otazky_ui[i][1]] = f"### Otázka {i+1}\n\n{q['q']}"
updates[otazky_ui[i][2]] = gr.update(choices=q["opts"], value=[])
else:
updates[otazky_ui[i][0]] = gr.update(visible=False)
updates[submit_btn] = gr.update(visible=True)
return updates
def submit_test(kod, typ, *args):
if not kod or not typ:
return "❌ Chyba: Test nebyl správně zahájen"
test = TESTS[typ]
odpovedi = {}
for i in range(len(test["otazky"])):
odpovedi[f"q_{i}"] = args[i] if i < len(args) else []
skore, max_skore = vyhodnotit_test(typ, odpovedi)
procenta = (skore / max_skore * 100)
ulozit_vysledek(kod, typ, skore, max_skore, odpovedi)
if typ == "STAFF":
uspech = skore >= 7
pozadavek = "min. 7 bodů"
else:
uspech = skore >= 8
pozadavek = "min. 8 bodů"
result = f"""
## {'✅ Úspěch!' if uspech else '❌ Neúspěch'}
**Váš výsledek:** {skore:.1f} / {max_skore} bodů ({procenta:.1f}%)
**Požadavek:** {pozadavek}
{'🎉 Gratulujeme! Test jste úspěšně absolvovali.' if uspech else '😔 Bohužel jste nesplnili minimální požadavek.'}
---
**Kód účastníka:** {kod}
**Datum:** {datetime.now().strftime('%d.%m.%Y %H:%M')}
"""
return result
def submit_feedback(kod, typ, obt, sroz, uzit, kom):
if not kod:
return "❌ Chyba při odesílání zpětné vazby"
ulozit_feedback(kod, typ, obt, sroz, uzit, kom)
return "✅ Děkujeme za zpětnou vazbu!"
# Events
start_btn.click(
fn=start_test,
inputs=[kod_input, typ_radio],
outputs=[start_msg, user_kod, user_typ, test_info] +
[item for group in otazky_ui for item in group] +
[submit_btn]
)
submit_btn.click(
fn=submit_test,
inputs=[user_kod, user_typ] + [q[2] for q in otazky_ui],
outputs=[result_md]
)
feedback_btn.click(
fn=submit_feedback,
inputs=[user_kod, user_typ, obtiznost, srozumitelnost, uzitecnost, komentar],
outputs=[feedback_msg]
)
results_btn.click(
fn=get_results_csv,
inputs=[admin_password],
outputs=[admin_output]
)
feedback_download_btn.click(
fn=get_feedback_csv,
inputs=[admin_password],
outputs=[admin_output]
)
if __name__ == "__main__":
demo.launch(share=True)