File size: 23,167 Bytes
c73b203
bef9357
c73b203
010abe9
bef9357
c73b203
bef9357
 
c73b203
bef9357
 
 
 
 
c73b203
 
 
43cfdee
bef9357
43cfdee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c73b203
 
 
43cfdee
bef9357
43cfdee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c73b203
 
 
 
bef9357
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43cfdee
bef9357
 
 
 
43cfdee
 
 
 
 
 
 
bef9357
 
 
 
 
010abe9
bef9357
 
43cfdee
 
 
 
 
bef9357
 
 
 
 
43cfdee
 
bef9357
43cfdee
 
 
 
bef9357
43cfdee
 
 
 
 
 
bef9357
 
 
 
43cfdee
 
 
010abe9
43cfdee
 
 
bef9357
43cfdee
bef9357
 
 
43cfdee
bef9357
43cfdee
 
 
 
 
 
 
 
c73b203
bef9357
 
 
43cfdee
 
 
c73b203
bef9357
 
c73b203
bef9357
43cfdee
 
 
bef9357
 
 
 
43cfdee
 
 
c73b203
43cfdee
 
c73b203
43cfdee
c73b203
bef9357
43cfdee
 
 
 
 
c73b203
 
 
43cfdee
 
 
 
 
 
 
 
 
 
 
010abe9
43cfdee
 
 
bef9357
43cfdee
bef9357
 
c73b203
bef9357
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
import gradio as gr
import json, csv, os, hashlib
from datetime import datetime

ADMIN_HASH = "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918"  # "admin"

RESULTS = "vysledky.csv"
FEEDBACK = "zpetna_vazba.csv"

for f, cols in [(RESULTS, ['cas','kod','typ','skore','max','procenta','odpovedi']), 
                (FEEDBACK, ['cas','kod','typ','obt_testu','sroz_testu','uzit_testu','obt_vyuky','sroz_vyuky','uzit_vyuky','komentar'])]:
    if not os.path.exists(f):
        with open(f, 'w', newline='', encoding='utf-8') as file:
            csv.writer(file).writerow(cols)

TESTS = {
    "STAFF": {
        "nazev": "Test pro administrativní pracovníky (STAFF)",
        "questions": [
            {
                "q": "Jaká je doporučená struktura efektivního promptu pro práci s AI?",
                "ctx": "Prompt je instrukce, kterou AI zadáváte. Jeho struktura ovlivňuje kvalitu odpovědí.",
                "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"
                ],
                "ans": ["A","C","E"]
            },
            {
                "q": "Co přesně znamená termín 'zero-shot prompting'?",
                "ctx": "Zero-shot je základní technika práce s AI, kdy modelu poskytujeme pouze instrukci bez dalších příkladů.",
                "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"
                ],
                "ans": ["B","D"]
            },
            {
                "q": "Co je 'answer pressure testing' a k čemu slouží?",
                "ctx": "Answer pressure testing je technika, kterou testujeme spolehlivost odpovědí AI a snižujeme riziko chyb.",
                "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"
                ],
                "ans": ["B","C","D"]
            },
            {
                "q": "Které z následujících úkolů lze bezpečně řešit pomocí AI v kancelářské práci?",
                "ctx": "AI je užitečná pro mnoho úkolů, ale ne všechny jsou pro ni vhodné nebo bezpečné.",
                "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"
                ],
                "ans": ["A","B","C","E"]
            },
            {
                "q": "Co přesně jsou 'halucinace' v kontextu AI systémů?",
                "ctx": "Halucinace jsou jedním z nejzávažnějších rizik při práci s AI a je důležité jim porozumět.",
                "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í"
                ],
                "ans": ["A","C","D"]
            },
            {
                "q": "Co je 'token' a proč je tento pojem důležitý?",
                "ctx": "Token je základní jednotka, se kterou AI systémy pracují. Pochopení tokenů je klíčové pro efektivní práci s AI.",
                "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"
                ],
                "ans": ["A","B","D"]
            },
            {
                "q": "Jak správně pracovat s AI při respektování GDPR?",
                "ctx": "GDPR chrání osobní údaje občanů EU. Při práci s AI je nutné dodržovat tyto právní požadavky.",
                "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í"
                ],
                "ans": ["A","C","D"]
            },
            {
                "q": "Jak správně pracovat s češtinou vs. angličtinou při použití AI?",
                "ctx": "Volba jazyka ovlivňuje kvalitu odpovědí AI systémů, protože byly trénovány primárně na anglických datech.",
                "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"
                ],
                "ans": ["A","C","E"]
            },
            {
                "q": "Co znamená 'chain-of-thought prompting' a kdy ho použít?",
                "ctx": "Chain-of-thought je pokročilá technika, kdy žádáme AI o postupné vysvětlení jednotlivých kroků řešení.",
                "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"
                ],
                "ans": ["A","C","D"]
            },
            {
                "q": "Co je 'role-playing' v kontextu práce s AI?",
                "ctx": "Role-playing je technika, kdy AI přiřazujeme konkrétní roli či identitu, což ovlivňuje styl a obsah odpovědí.",
                "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"
                ],
                "ans": ["A","B","D"]
            }
        ]
    },
    "ACADEMIC": {
        "nazev": "Test pro akademické pracovníky (ACADEMIC)",
        "questions": [
            {
                "q": "Jaká je struktura akademického promptu?",
                "ctx": "Akademická práce vyžaduje přesnější a rigoróznější strukturu promptů než běžné použití AI.",
                "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ý"
                ],
                "ans": ["A","C","D"],
                "pts": 1
            },
            {
                "q": "Co je 'answer pressure testing' a proč je v akademii kritický?",
                "ctx": "V akademickém kontextu je spolehlivost informací zásadní, proto je answer pressure testing klíčovou technikou.",
                "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"
                ],
                "ans": ["A","B","C","E"],
                "pts": 1
            },
            {
                "q": "Proč jsou halucinace v akademii zvlášť problematické?",
                "ctx": "V akademickém výzkumu může jediná chyba diskreditovat celou práci. Halucinace AI představují závažné riziko.",
                "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é"
                ],
                "ans": ["A","C","D"],
                "pts": 1
            },
            {
                "q": "Rozdíl mezi 'asistencí' a 'nahrazením úsudku'?",
                "ctx": "V akademické práci je klíčové rozlišovat, kdy AI pomáhá a kdy za nás myslí. To má etické i metodologické důsledky.",
                "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é"
                ],
                "ans": ["A","B","D"],
                "pts": 1
            },
            {
                "q": "Jak správně pracovat s AI a GDPR v akademickém výzkumu?",
                "ctx": "Akademický výzkum často pracuje s citlivými daty respondentů. GDPR stanovuje přísné požadavky na jejich ochranu.",
                "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"
                ],
                "ans": ["A","C","D"],
                "pts": 1
            },
            {
                "q": "Proč je pro akademii doporučena angličtina?",
                "ctx": "AI modely byly trénovány primárně na anglických akademických textech, což má důsledky pro kvalitu odpovědí.",
                "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á"
                ],
                "ans": ["A","C","D"],
                "pts": 1
            },
            {
                "q": "Které použití AI je přijatelné a které ne?",
                "ctx": "V akademické práci existují jasné hranice mezi přijatelným a nepřijatelným použitím AI. Je klíčové je rozlišovat.",
                "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ů"
                ],
                "ans": ["A","C","D"],
                "pts": 1.5
            },
            {
                "q": "Jak pracovat s epistemickou nejistotou?",
                "ctx": "AI systémy nemají skutečné porozumění a často si nejsou jisté. Musíme s touto nejistotou pracovat systematicky.",
                "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"
                ],
                "ans": ["A","C","D"],
                "pts": 1.5
            },
            {
                "q": "Jak správně pracovat se zdroji a citacemi?",
                "ctx": "AI systémy mají tendenci vymýšlet realisticky vypadající citace. To představuje závažné riziko pro akademickou integritu.",
                "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'"
                ],
                "ans": ["A","B","C","E"],
                "pts": 1.5
            },
            {
                "q": "Co musí akademik uvést ohledně použití AI?",
                "ctx": "Transparentnost je základem akademické integrity. Použití AI nástrojů musí být jasně deklarováno.",
                "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"
                ],
                "ans": ["B","C","D","E"],
                "pts": 1.5
            },
            {
                "q": "Proč AI nemá 'skutečné porozumění'?",
                "ctx": "Pochopení limitů AI je klíčové pro její zodpovědné použití v akademickém kontextu.",
                "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"
                ],
                "ans": ["A","B","C","E"],
                "pts": 1.5
            },
            {
                "q": "Jaká je role AI v akademickém výzkumu?",
                "ctx": "Je důležité správně definovat roli AI v akademické práci - co může a co nemůže dělat.",
                "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í"
                ],
                "ans": ["A","C","E"],
                "pts": 1.5
            }
        ]
    }
}

def check(pwd):
    return hashlib.sha256(pwd.encode()).hexdigest() == ADMIN_HASH

def score(typ, ans):
    correct = sum(q.get("pts",1) for i,q in enumerate(TESTS[typ]["questions"]) 
                  if set(ans.get(f"q_{i}",[]))==set(q["ans"]))
    total = sum(q.get("pts",1) for q in TESTS[typ]["questions"])
    return correct, total

def save_result(kod, typ, sc, mx, ans):
    with open(RESULTS, 'a', newline='', encoding='utf-8') as f:
        csv.writer(f).writerow([datetime.now().isoformat(), kod, typ, sc, mx, 
                                f"{sc/mx*100:.1f}%", json.dumps(ans, ensure_ascii=False)])

def save_fb(kod, typ, ot, st, ut, ov, sv, uv, kom):
    with open(FEEDBACK, 'a', newline='', encoding='utf-8') as f:
        csv.writer(f).writerow([datetime.now().isoformat(), kod, typ, ot, st, ut, ov, sv, uv, kom])

def get_file(pwd, which):
    if not check(pwd):
        return None, "❌ Špatné heslo"
    file = RESULTS if which=="r" else FEEDBACK
    if not os.path.exists(file):
        return None, "📭 Zatím žádná data"
    return file, f"✅ {file} připraven ke stažení"

with gr.Blocks(title="Test AI KTF") as app:
    gr.Markdown("# 🎓 Test znalostí: Generativní AI\n**Katolická teologická fakulta UK, Praha 2025**")
    kod_s, typ_s = gr.State(""), gr.State("")
    
    with gr.Tab("🔐 Start"):
        gr.Markdown("### Zahájení testu")
        gr.Markdown("Pro zahájení zadejte váš unikátní kód a vyberte typ testu.")
        kod = gr.Textbox(label="Váš unikátní kód", placeholder="KTF2025-001", info="Tento kód obdržíte od organizátora")
        typ = gr.Radio([
            ("STAFF - Administrativní pracovníci (10 otázek)", "STAFF"), 
            ("ACADEMIC - Výzkumní pracovníci (12 otázek)", "ACADEMIC")
        ], label="Typ testu", info="Vyberte test odpovídající vaší roli")
        go = gr.Button("Začít test", variant="primary", size="lg")
        msg = gr.Markdown("")
    
    with gr.Tab("📝 Test"):
        info = gr.Markdown("")
        qs = []
        for i in range(12):
            with gr.Group(visible=False) as g:
                qt = gr.Markdown("")
                qctx = gr.Markdown("", elem_classes="question-context")
                qc = gr.CheckboxGroup(label="⚠️ Vyberte VŠECHNY správné odpovědi")
                qs.append((g,qt,qctx,qc))
        send = gr.Button("Odeslat test", variant="primary", size="lg", visible=False)
        send_msg = gr.Markdown("")
    
    with gr.Tab("📊 Výsledky"):
        res = gr.Markdown("")
        
        gr.Markdown("---")
        gr.Markdown("### 💬 Zpětná vazba")
        gr.Markdown("*Vaše zpětná vazba je anonymní a pomůže nám zlepšit jak test, tak celou výuku.*")
        
        gr.Markdown("#### Hodnocení testu:")
        obt_t = gr.Radio(["Příliš snadný", "Přiměřený", "Příliš těžký"], label="Obtížnost testu")
        sroz_t = gr.Radio(["Výborná", "Dobrá", "Špatná"], label="Srozumitelnost otázek")
        uzit_t = gr.Radio(["Velmi užitečný", "Užitečný", "Málo užitečný"], label="Užitečnost testu pro praxi")
        
        gr.Markdown("#### Hodnocení výuky (kurzu AI):")
        obt_v = gr.Radio(["Příliš snadná", "Přiměřená", "Příliš těžká"], label="Obtížnost výuky")
        sroz_v = gr.Radio(["Výborná", "Dobrá", "Špatná"], label="Srozumitelnost výkladu")
        uzit_v = gr.Radio(["Velmi užitečná", "Užitečná", "Málo užitečná"], label="Užitečnost pro praxi")
        
        kom = gr.Textbox(label="Váš komentář (nepovinné)", placeholder="Sdílejte své postřehy k testu nebo výuce...", lines=3)
        fb = gr.Button("Odeslat zpětnou vazbu", variant="secondary")
        fb_msg = gr.Markdown("")
    
    with gr.Tab("👨‍💼 Admin"):
        gr.Markdown("### 🔒 Administrátorský přístup")
        gr.Markdown("*Pouze pro organizátory kurzu - stahování výsledků*")
        pwd = gr.Textbox(label="Admin heslo", type="password", placeholder="Výchozí: admin")
        with gr.Row():
            dlr = gr.Button("📥 Stáhnout výsledky testů")
            dlf = gr.Button("📥 Stáhnout zpětnou vazbu")
        dl = gr.File(label="CSV soubor ke stažení")
        dlm = gr.Markdown("")
        gr.Markdown("*Pro změnu hesla upravte ADMIN_HASH v app.py*")
    
    def start(k, t):
        if not k or not t:
            return {msg: "❌ Prosím vyplňte kód i typ testu", kod_s: "", typ_s: ""}
        test = TESTS[t]
        upd = {
            msg: f"✅ Test byl zahájen! **→ Přejděte na záložku 'Test'**", 
            kod_s: k, 
            typ_s: t, 
            info: f"## {test['nazev']}\n\n**Počet otázek:** {len(test['questions'])}\n\n⚠️ **Důležité:** U každé otázky označte VŠECHNY správné odpovědi!", 
            send: gr.update(visible=True),
            send_msg: ""
        }
        for i in range(12):
            if i < len(test["questions"]):
                q = test["questions"][i]
                upd[qs[i][0]] = gr.update(visible=True)
                upd[qs[i][1]] = f"### Otázka {i+1}\n\n{q['q']}"
                upd[qs[i][2]] = f"*{q.get('ctx', '')}*"
                upd[qs[i][3]] = gr.update(choices=q["opts"], value=[])
            else:
                upd[qs[i][0]] = gr.update(visible=False)
        return upd
    
    def submit(k, t, *a):
        if not k: 
            return "❌ Chyba: Test nebyl správně zahájen", ""
        
        ans = {f"q_{i}": a[i] for i in range(len(TESTS[t]["questions"]))}
        sc, mx = score(t, ans)
        save_result(k, t, sc, mx, ans)
        ok = sc >= (7 if t=="STAFF" else 8)
        poz = "minimálně 7 bodů" if t=="STAFF" else "minimálně 8 bodů"
        
        result = f"""## {'✅ Gratulujeme! Test úspěšně splněn!' if ok else '❌ Test nesplněn'}

**Váš výsledek:** {sc:.1f} / {mx} bodů ({sc/mx*100:.1f}%)  
**Požadavek pro splnění:** {poz}

{'🎉 **Úspěšně jste absolvovali test!**' if ok else '😔 Bohužel jste nedosáhli minimálního počtu bodů. Doporučujeme prostudovat materiály a zkusit test znovu.'}

---

**📊 Podrobnosti:**
- Kód účastníka: {k}
- Typ testu: {t}
- Datum a čas: {datetime.now().strftime('%d.%m.%Y v %H:%M')}

---

### ⬇️ Co dál?

**Prosím, poskytněte nám zpětnou vazbu níže** - pomůžete nám zlepšit jak test, tak celou výuku. Zpětná vazba je anonymní a zabere jen 2 minuty. Děkujeme! 🙏
"""
        
        nav_msg = "# ✅ Test odeslán!\n\n**→ Přejděte na záložku 'Výsledky' pro zobrazení výsledků a zpětnou vazbu**"
        
        return result, nav_msg
    
    go.click(start, [kod,typ], 
             [msg,kod_s,typ_s,info,send,send_msg]+[x for g in qs for x in g])
    
    send.click(submit, [kod_s,typ_s]+[q[3] for q in qs], [res, send_msg])
    
    fb.click(lambda k,t,ot,st,ut,ov,sv,uv,c: (save_fb(k,t,ot,st,ut,ov,sv,uv,c), "✅ **Děkujeme za zpětnou vazbu!** Vaše připomínky jsou pro nás velmi cenné.")[1] if k else "❌ Nelze odeslat - test nebyl dokončen", 
             [kod_s,typ_s,obt_t,sroz_t,uzit_t,obt_v,sroz_v,uzit_v,kom], fb_msg)
    
    dlr.click(lambda p: get_file(p,"r"), pwd, [dl,dlm])
    dlf.click(lambda p: get_file(p,"f"), pwd, [dl,dlm])

app.launch()