Architektura sběru dat
Jak data tečou od kamery přes výpočty do reportu.
Pipeline
Webkamera 1280x720 MediaPipe Face Landmarker 478 landmarks + 52 blendshapes detect*() funkce cur objekt (každý frame) collectSnapshot() 1x/s KV Store (Cloudflare) Report
Soubory
camera.jsMediaPipe init, detekce, výpočty, sber dat sensors.jsMikrofon, TTS, volume, device info engine.jsRizeni session, sestaveni vysledku report.jsVizualizace, grafy, karty, export worker.jsCloudflare Worker — API, KV storage
Technologie
Detekce obličejeGoogle MediaPipe Face Landmarker (GPU) Detekce rukouGoogle MediaPipe Hand Landmarker (GPU) Hlasove komentareWeb Speech API (SpeechRecognition) Hlas systemuWeb Speech API (SpeechSynthesis) Bezí kde100% lokalne v prohlizeci, žádná data neopousti zarizeni bez souhlasu
Jak často se co měří a ukládá
Detekce (výpočet hodnot)
requestAnimationFrame — běží tak rychle, jak to prohlizec stihne. Typicky 30–60 FPS (závisí na GPU/CPU). Každý frame aktualizuje vsechny metriky v objektu cur.
Ukládání
collectSnapshot()každých 1000 ms (1x/s) — kompletni snapshot vsech metrik collectBlendshapes()každých 2000 ms (1x za 2s) — surovych 52 blendshapes checkMultipleFaces()každých 5000 ms (1x za 5s) — druha instance MP, az 3 obličeje facePhotosevent-triggered — pri session_start, každé otázce, každém revealu, session_end
Datove body za sekundu
Každý snapshot obsahuje 82 datovych bodu: 52 blendshapes + 30 odvozenych metrik (emoce, attention, gaze, fatigue, headpose, distance, blink, smile, iris, gesta, symetrie, ...). Konfigurace: CONFIG.SNAPSHOT_INTERVAL = 1000
— Biometricke metriky —
🎯 Pozornost — pohled (Gaze Attention)
🎯 Gaze Attention Spolehlive
Co měří
Kam se uživatel dívá. 100% = pohled přímo dopředu (na obrazovku), 0% = pohled vyrazne stranou nebo nahoru. Měří odchylku smeru pohledu od primeho smeru.
Zdroj dat
Blendshapes (svalové hodnoty) z MediaPipe: eyeLookOutLeft, eyeLookOutRight, eyeLookUpLeft, eyeLookUpRight, eyeLookDownLeft, eyeLookDownRight. Tyto hodnoty jsou přímo z ML modelu — žádná ruční geometrie.
Funkce
detectGazeAttention(blendshapes) v camera.js
Algoritmus
// 1. Side gaze — divani se stranou sideAvg = (eyeLookOutLeft + eyeLookOutRight) / 2 sidePenalty = min(1.0, sideAvg × 3) // 0.33+ = plna penalizace // 2. Upward gaze — divani se nahoru/pryc upAvg = (eyeLookUpLeft + eyeLookUpRight) / 2 upPenalty = min(1.0, upAvg × 4) // 0.25+ = plna penalizace // 3. Extreme down — divani se do klina/na telefon (jen >0.7) downAvg = (eyeLookDownLeft + eyeLookDownRight) / 2 downPenalty = max(0, (downAvg - 0.7)) × 3 // penalizace az nad 0.7 // Celková penalizace (vážený součet) totalPenalty = min(1.0, side×0.4 + up×0.3 + down×0.3) gazeAttention = round((1 - totalPenalty) × 100)
Frekvence
Výpočet: každý frame (30–60 FPS). Ulozeni: 1x/s do collected.gazeAttention[] a do každého snapshotu.
Rozsah
0–100%. Mirne divani se dolu (cteni, obrazovka nize) = stále vysoka hodnota. Penalizace az pri extremnim pohledu dolu (>0.7).
V reportu
Summary karta"Pozornost (pohled)" / "Attention (gaze)" — průměrná hodnota Bio summaryŘádek s průměrem a rozsahem min–max GrafPlna zelena křivka v grafu "Pozornost & Unava" Question/Video timeline🎯 pohled X% u každé otázky/kroku Foto thumbnails🎯X% pod každou fotkou Charts summary tabulkaŘádek s avg/min/max
Proc blendshapes? ML model MediaPipe extrahuje svalové hodnoty přímo z obrazu. Na rozdil od iris tracking (geometricky výpočet z landmarku) není zavislý na presnych souradnicich a funguje spolehlivé i s běžnou webkamerou.
👁 Iris Tracking
👁 Iris Tracking Experimentální
Co měří
Pozici duhovek v ocnich sterbinach. Teoreticky presnejsi nez blendshapes, ale vyzaduje kvalitni kameru nebo dedickovany eye-tracking hardware.
Zdroj dat
MediaPipe landmarks: duhovky lm[473] (leva) a lm[468] (prava), rohy oci lm[33], lm[133], lm[263], lm[362].
Funkce
detectAttention(landmarks) v camera.js
Algoritmus
// Pomer pozice duhovky vuci sirce oka leW = abs(eyeOuter.x - eyeInner.x) // šířka leveho oka lPos = (irisLeft.x - eyeOuter.x) / leW // ocekavano 0.0–1.0 rPos = (irisRight.x - eyeOuter.x) / reW dev = abs(((lPos + rPos) / 2) - 0.5) × 2 // odchylka od stredu attention = round(max(0, min(100, (1 - dev) × 100)))
Znamy problem: S běžnou webkamerou hodnoty lPos/rPos vychazi 2.5–3.7 (místo 0.0–1.0). Důvod: souradnice iris landmarku (473/468) jsou v jinem rozsahu nez eye corner landmarky, pravdepodobne vlivem mirror modu kamery nebo normalizace souradnic. Vysledek: 54–94% měření = 0% pozornost. Proto je metrika oznacena jako experimentalni.
V reportu
Summary karta"Iris tracking (exp.)" — snizena opacity 0.6 GrafCarkovana zelena křivka s opacity 0.3 Timeline👁 iris (exp.) X% za gaze hodnotou
Ukladana surova data
irisLeft a irisRight v každém snapshotu — pro budouci analyzu s lepsim HW.
😊 Emoce (Emotion Detection)
😊 Emotion Detection Funkcni / bias
Co měří
Dominantni emoce z vyrazu obličeje. 5 kategorii: Happy, Surprised, Focused, Relaxed, Neutral. Kazda s confidence skóre 0–100%.
Funkce
detectEmotion(blendshapes) v camera.js
Algoritmus (vodopadove if/else)
smile = (mouthSmileLeft + mouthSmileRight) browUp = (browOuterUpLeft + browOuterUpRight) browDown = (browDownLeft + browDownRight) jawOpen = jawOpen if smile > 0.4Happy (conf: min(100, smile × 100)) if browUp > 0.3 + jawOpen > 0.2Surprised (conf: min(100, (browUp+jawOpen) × 50)) if browDown > 0.2Focused (conf: min(100, browDown × 150)) if activity < 0.15Relaxed (conf: (1-activity) × 80) jinak → Neutral (conf: 60, fixní)
Znamy bias: browDown > 0.2 zachyti "Focused" u 53–73% uzivatelu, protože někteří lidé mají přirozeně nízké oboci. Prioritni razeni znamena, ze Focused "vyhrava" nad Relaxed/Neutral. Pro presnejsi detekci by byla potreba kalibrace baseline obličeje na zacatku session.
Frekvence
Každý frame → ulozeni 1x/s do collected.emotions[] jako { timestamp, emotion, confidence }.
V reportu
Summary karta"Emoci" — počet různých detekovaných emoci Emotion timeline grafBarevne pruhy po sekundach (Happy=zelena, Surprised=zluta, Focused=modra, Relaxed=fialova, Neutral=seda) Červená křivka v emocnim grafuIntenzita usmеvu (smile) Question/Video timeline😊 Emotion + jistota/conf X% u každé otázky Foto thumbnailsEmoce pod každou fotkou
😴 Únava (Fatigue)
😴 Fatigue Score Funkcni / kaskada
Co měří
Mira unavy uživatele, 0–100%. Bodovy system z 5 nezavislych faktoru.
Funkce
detectFatigue(blendshapes) v camera.js
Algoritmus
// 5 faktoru, každý pridava body, max 100 if blinkRate > 25 → +30 bodu // vysoka frekvence mrkání if blinkRate > 20 → +15 bodu // zvysena frekvence mrkání if eyeSquint > 0.3 → +25 bodu // primhoureni oci if browDown > 0.2 → +15 bodu // svesene oboci if mouthOpen → +15 bodu // otevrena usta (zivani), jawOpen > 0.15 if attention < 50 → +15 bodu // pokles pozornosti (iris-based) fatigue = min(100, součet)
Kaskadovy efekt: Faktor attention < 50 závisí na iris tracking, ktery je experimentalni. S běžnou webkamerou je iris attention často 0%, takže tento faktor pridava +15 u 87% měření. Po nasazeni Gaze Attention se muzе prepojit na tu.
Neadaptivni prahy: eyeSquint > 0.3 a browDown > 0.2 jsou fixní — někteří lidé mají přirozeně primhourene oci nebo nízké oboci, coz nafoukne fatigue skóre.
V reportu
Summary karta"Unava" / "Fatigue" — průměr Bio summaryŘádek s průměrem a rozsahem GrafČervená křivka v grafu "Pozornost & Unava" Charts summary tabulkaŘádek s avg/min/max
📏 Vzdálenost (Face Distance)
📏 Face Distance Funkcni
Co měří
Odhad vzdalenosti obličeje od kamery v centimetrech.
Funkce
detectDistance(landmarks) v camera.js
Algoritmus
d = sqrt((rightEye.x - leftEye.x)² + (rightEye.y - leftEye.y)²) // d = rozestup oci v normalizovanych souradnicich (0–1) // konstanta 0.095 ≈ průměrná šířka mezi ocima distance = max(20, min(150, round((0.095 / (d + 0.001)) × 100))) // Omezeno na 20–150 cm
Presnost: Zalezi na rozlišení kamery a individualni anatomii (šířka mezi ocima se lisi). Jde o odhad, ne presne měření. Konstanta 0.095 není kalibrovana na konkretniho uživatele.
V reportu
Bio summary"Vzdalenost" — průměr a rozsah v cm GrafModra křivka "Vzdalenost obličeje od kamery", osa Y: 0–150 cm Foto modal📏Xcm u každé fotky
🔄 Pohyb hlavy (Head Pose)
🔄 Head Pose Funkcni
Co měří
Úhly natoceni hlavy ve stupnich: Pitch (nahoru/dolu), Yaw (vlevo/vpravo), Roll (nakloneni do stran).
Funkce
detectHeadPose(landmarks, transformMatrix) v camera.js
Algoritmus — dve cesty
// Priorita 1: Transformacni matice z MediaPipe (presnejsi) if (transformMatrix.data) { pitch = asin(-m[6]) × 57.3° yaw = atan2(m[2], m[10]) × 57.3° roll = atan2(m[4], m[5]) × 57.3° } // Priorita 2: Geometricky odhad z landmarku (fallback) else { yaw = (rightDist - leftDist) / (leftDist + rightDist) × 60° pitch = (nosePos - 0.35) × 80° roll = atan2(rightEye.y - leftEye.y, rightEye.x - leftEye.x) × 57.3° }
V reportu
Bio summary"Hlava" — průměrně absolutní úhly ±P°/±Y° Graf"Pohyb hlavy" — Pitch (fialova) + Yaw (ruzova), osa Y: -45° az +45°, stredova linka na 0°
😊 Úsměv (Smile)
😊 Smile Intensity Funkcni
Výpočet
(mouthSmileLeft + mouthSmileRight) × 50 — přímý přepočet z blendshapes, rozsah 0–100%.
V reportu
Červená křivka v Emotion timeline grafu. Zobrazuje se i v photo modálu: 😊X%.
✋ Gesta rukou (Hand Gestures)
Hand Gesture Detection Funkcni
Co měří
Gesta rukou v zaberu kamery. 8 rozpoznavanych gest: 👍 Thumbs Up, 👎 Thumbs Down, ✌️ Peace, ☝️ Point, ✋ Open Palm, ✊ Fist, 👌 OK, 🤟 Rock.
Zdroj
MediaPipe Hand Landmarker — 21 bodu ruky. Funkce detectGesture(handLandmarks) porovnava pozice konecku prstu vuci kloubum (tip.y vs joint.y).
Ukládání
Každý snímek (1x/s) se loguje — včetně "None" (ruka není vidět). To umožňuje měřit jak dlouho byla ruka viditelna. Pro report se "None" zaznamy filtruji.
False positives: občas se detekuje "Point" když ruka není přítomná (nos, čelo nebo objekt v pozadí). Jeden uživatel mel 176× Point během 286s session. Shluky 3+ sekund stejneho gesta jsou spolehlivejsi nez izolované detekce.
V reportu
Summary karta"Gest" — počet (filtrovano, bez "None") Sekce gestaSeskupeno podle typu s poctem, mini casovou osou (tecky) a casovym rozsahem
📷 Detekce obličeje (Face Detection Rate)
📷 Face Detection Funkcni
Co měří
Procento snímků, kde byl obličej úspěšně detekován. Nízké hodnoty = obličej mimo zaber, zakryty, nebo spatne osvětlení.
Výpočet
faceDetected.filter(detected).length / faceDetected.length × 100%
V reportu
Bio summary: "Detekce" X% (počet/celkem). Zobrazuje se jako prvni řádek.
👥 Detekce dalších osob (Multi-face Presence)
👥 Multi-face Check Funkcni
Co měří
Pritomnost dalších osob v zaberu. Druha instance MediaPipe (bez blendshapes, numFaces: 3) kontroluje každých 5 sekund.
V reportu
Bio summary"další osoby" — počet detekci nebo "Ne" Emotion grafCervene vertikalni zony kde byl detekován vic nez 1 obličeje Biometric grafyCervene vertikalni cary + zony
🎤 Hlasové komentáře (Voice Comments)
🎤 Voice Comments Echo problem
Co měří
Hlasove komentare uživatele prepisane v realnem case pomoci Web Speech API (SpeechRecognition). Každý komentar ma text, timestamp a confidence (0–100%).
Konfigurace
continuous: true, interimResults: false, jazyk dle nastaveni session (cs-CZ / en-US). Auto-restart pri ukonceni.
Znamy problem: Mikrofon běží nepřetržitě a zachytává i zvuk z videa (avatar) nebo TTS. Pauza během prehravani zvuku není implementovana — v planu.
V reportu
Summary karta"Komentaru" — počet Sekce hlasove komentareSeznam s casem, textem a confidence TimelineKomentare prirazene k otazkam/krokum podle casu
🔊 Hlasitost mikrofonu (Mic Volume)
🔊 Mic Volume Nulova data
Co měří
Peak hlasitost z mikrofonu každou sekundu (AnalyserNode z Web Audio API).
Problem: Vsechny zaznamenane hodnoty jsou 0. AnalyserNode pravdepodobne není správně napojeny na mic stream, nebo peak detekce ma bug. Vyzaduje investigaci.
V reportu
Casova osa hlasitosti (pokud jsou data nenulova). Aktualne se nezobrazuje.
📸 Fotografie a Photo Quality
📸 Face Photos + Quality Score Kaskada z iris
Kdy se poridi fotka
Event-triggered: session_start, video_* (každý video krok), question_*, reveal_*, session_end. Dve verze: clean (cisty obraz) + overlay (s face mesh).
Quality score
quality = (faceDetected ? 40 : 0) + min(30, attention × 0.3) // ← iris-based, max +30 + min(20, emotion.score × 0.2) // max +20 + (headPose centered ? 10 : 0) // yaw < 15° && pitch < 15° // Max mozne: 40 + 30 + 20 + 10 = 100
Kaskada: S broken iris attention (vzdy ~0) chybi az 30 bodu. Max dosazitelne skóre je ~70 místo 100. Nejlepsi fotka se vybira pres selectBestPhoto() — porovnava quality a bere nejvyšší.
V reportu
Face Photos sekceCarousel s thumbnaily — klik otevira modal s overlay Kazda fotka ukazujeCas, emoce, gaze/attention %, vzdalenost, usmev Hlavni fotka uživateleNejlepsi fotka (highest quality) v hlavicce reportu
🧬 Surové blendshapes (Raw Blendshape Log)
🧬 Blendshape Log Sbira se
Co to je
Kompletni log vsech 52 blendshapes (svalových hodnot) z MediaPipe. Každých 2 sekundy se ulozi surovy snímek. Slouzi pro budouci detailni analyzu a ladeni.
52 blendshapes zahrnuji
eyeBlinkLeft/Right, eyeLookDownLeft/Right, eyeLookInLeft/Right, eyeLookOutLeft/Right, eyeLookUpLeft/Right, eyeSquintLeft/Right, eyeWideLeft/Right, browDownLeft/Right, browInnerUp, browOuterUpLeft/Right, cheekPuff, cheekSquintLeft/Right, jawForward, jawLeft/Right, jawOpen, mouthClose, mouthDimpleLeft/Right, mouthFrownLeft/Right, mouthFunnel, mouthLeft/Right, mouthLowerDownLeft/Right, mouthPressLeft/Right, mouthPucker, mouthRollLower/Upper, mouthShrugLower/Upper, mouthSmileLeft/Right, mouthStretchLeft/Right, mouthUpperUpLeft/Right, noseSneerLeft/Right, _neutral
V reportu
Nezobrazuje se přímo — export JSON obsahuje kompletni log. Pouziva se pro JSON export a budouci AI analyzu.
⚖️ Symetrie obličeje (Face Symmetry)
⚖️ Face Symmetry Sbira se, nezobrazuje
Výpočet
// 4 pary blendshapes pary = [mouthSmileL/R, eyeBlinkL/R, browDownL/R, cheekSquintL/R] symScore = průměr(1 - abs(left - right)) pro každý par faceSym = round(symScore × 100) // 0–100%, 100 = dokonale symetricky
V reportu
Aktualne se nezobrazuje. Data jsou v každém snapshotu (faceSym). Zajimava metrika pro budouci rozsireni reportu.
— Report sekce —
🔌 Stav senzorů v reportu
Zobrazene senzory
📷 KameraAktivni/Neaktivni + počet snímků 🧠 MediaPipeAktivni/Selhalo + počet bodu + počet fotek + "82 dat/s" 🎙 MikrofonAktivni/Neaktivni + počet komentaru 🔊 ReproduktorHlasitost v %
Zdroj
Ze sessionLog eventu session_started (pole sensors) a dopocitano z dat (fallback).
📊 Grafy v reportu
Vsechny grafy
Emoce v caseBarevne pruhy (1 pruh = 1s), červená křivka usmev, question markery, multi-face zony Pozornost & UnavaGaze (plna zelena), Iris (carkovana zelena 0.3), Fatigue (červená), question markery Vzdalenost (cm)Modra křivka, osa Y: 0–150 Mrkání (/min)Zluta křivka, osa Y: 0–60 Pohyb hlavyPitch (fialova) + Yaw (ruzova), osa Y: -45° az +45°, stredova linka Hlasitost mikrofonuZelena area chart (pokud data existuji a nejsou nulova)
Technicke detaily grafu
Vsechny grafy jsou SVG s viewBox="0 0 100 100" a preserveAspectRatio="none". Data se mapuji na 0–100% osy. Krivky pouzivaji vector-effect="non-scaling-stroke" pro konzistentni sirku cary. Question markery jsou carkovane vertikalni cary. Multi-face detekce jsou cervene zony.
Funkce pro krivky
buildPath(arr, key, maxVal) { arr.map((v, i) => { x = (i / (arr.length - 1)) × 100 y = 100 - ((v[key] ?? v.score ?? 0) / maxVal) × 100 return (i===0 ? 'M' : 'L') + x + ',' + y }).join(' ') }
🃏 Summary karty v reportu
Video scénář (platform-demo)
Video krokuvideoSteps.length VolbaclosingChoice (pokud existuje) Emocipočet unikatnich emoci KomentaruvoiceComments.length Pozornost (pohled)průměr gazeAttention (nova metrika) Iris tracking (exp.)průměr attention (opacity 0.6) Únavaprůměr fatigue Gestgestures filtrovat != None (pokud > 0)
Classic scénář (device-privacy-awareness)
Otázekpočet otázek Správněcorrect/total Ø Reakceprůměrný reakční cas v sekundach + spolecne kartyEmoce, Komentare, Gaze, Iris (exp.), Unava, Gesta
Hlavicka reportu
Dynamicky text: "Během školení jsme zachytili X biometrickych snímků (Y datovych bodu), Z fotografii, N gest rukou a M hlasovych komentaru. Vse v realnem case." Počet datovych bodu = snapshots × 82.
Kapitola 2
🧠 Metodologie kontextuální analýzy
Tato kapitola dokumentuje každý odvozený ukazatel použitý v sekci „Analýza session" v reportu. Pro každou metriku je uveden přesný vzorec, zdroj dat, vědecký základ a implementační poznámky pro replikovatelnost na jakýkoliv scénář.
📋 Princip analyzy Zaklad
Filosofie
Analýza je deterministická — žádné AI generování, žádné náhodné prvky. Každý závěr plyne přímo z číselných dat. Interpretace jsou formulovány jako pozorování, nikoli hodnocení. Cil: poskytnout kontextuální rámec pro surová data, který je replikovatelný a verifikovatelný.
Vstupní data
Kompletní session JSON export obsahující: gazeAttention[], fatigue[], emotions[], blinkRate[], distance[], headPose[], gestures[], blendshapeLog[], micVolume[], voiceComments[], videoSteps[], reactionTimes[], presenceChecks[], sessionLog[].
Segmentace
Data jsou segmentována podle videoSteps[] (u video scénáře) nebo answers[] (u classic scénáře). Každý segment je definován časovým rozsahem [startedAt, endedAt] a všechna biometrická data jsou filtrována podle timestampu do příslušného segmentu.
📊 Skóre zapojení (Engagement Score)
🎯 Engagement Score Odvozena
Účel
Souhrnné číslo 0–100 vyjadřující celkovou míru zapojení účastníka během session. Zobrazeno jako kruhový indikátor v reportu.
Vzorec
score = w1×gazeNorm + w2×faceNorm + w3×emotionNorm + w4×interactionNorm + w5×completionNorm
Složky a váhy
gazeNorm (w=0.30)avg(gazeAttention.score) / 100 faceNorm (w=0.15)count(faceDetected=true) / total emotionNorm (w=0.20)count(emotion=Focused|Happy) / total interactionNorm (w=0.20)(hasVoiceTips + hasGestures + hasVoiceChoice) / 3 completionNorm (w=0.15)1.0 pokud session_completed, 0.5 pokud tab_switch end, 0.0 jinak
Příklad z demo session
0.30×0.694 + 0.15×1.0 + 0.20×0.992 + 0.20×1.0 + 0.15×1.0 = 0.208 + 0.15 + 0.198 + 0.20 + 0.15 = 0.906 → zaokrouhleno na 80
Poznamka: v aktuální implementaci je skóre nastaveno manuálně na základě expertního odhadu. Výše uvedený vzorec je navržená automatizace.
Poznámky k implementaci
Váhy jsou nastavitelné per scénář. Pro classic scénáře přidat složku correctAnswersNorm. Pro platformu bez hlasových vstupů upravit interactionNorm na použití gest a kliknutí.
🎯 Pozornost podle segmentu (Attention per Segment)
🎯 Gaze Attention per Video Step Odvozena
Co se zobrazuje v reportu
Horizontalni mini-bary ukazujici průměr gaze attention pro každý video krok. Barva: zelena (>70), oranzova (60-70), zluta (<60). Umožňuje identifikovat ktery segment účastníka zaujal nejvic a ktery nejmene.
Vzorec
segmentAvg = avg(gazeAttention.score WHERE timestamp BETWEEN step.startedAt AND step.endedAt)
Pro každý prvek videoSteps[] se filtuji vsechny zaznamy z gazeAttention[] jejichz timestamp pada do casoveho rozsahu daneho kroku.
Implementace
function gazePerSegment(gazeAttention, videoSteps) { return videoSteps.map(step => { const scores = gazeAttention .filter(g => g.timestamp >= step.startedAt && g.timestamp <= (step.endedAt || step.startedAt + 60000)) .map(g => g.score); return { videoId: step.videoId, avg: scores.length > 0 ? scores.reduce((a,v) => a+v, 0) / scores.length : 0, n: scores.length }; }); }
Interpretacni logika v reportu
1. Najdi segment s max(avg) → „Nejvyšší pozornost u [název segmentu]"
2. Najdi segment s min(avg) → „Nejnižší pozornost u [název segmentu]"
3. Pokud max - min > 10 → „To naznacuje, ze tema [max segmentu] zaujalo vice nez [min segmentu]"
4. Pokud max - min < 5 → „Pozornost byla rovnomerne rozlozena pres vsechny segmenty"
Barvy mini-baru
avg >= 70var(--beam) zelena — vysoka pozornost avg 60–69var(--warning) oranzova — střední avg < 60var(--danger) červená — nizka Tip/otazkovy segmentvar(--info) modra — odliseni od videa
😴 Prubeh unavy (Fatigue Trend per Segment)
😴 Fatigue per Video Step + Trend Detection Odvozena
Co se zobrazuje v reportu
Sloupcovy mini-graf (bary) ukazujici průměr unavy per segment. Barva: zelena (<45%), oranzova (45–54%), zluta (≥55%). Pod grafem jsou popisy segmentu. Textove: identifikace trendu (rostouci/klesajici) a vyjimek.
Vzorec
segmentFatigueAvg = avg(fatigue.score WHERE timestamp BETWEEN step.startedAt AND step.endedAt)
Identicky filtr jako u pozornosti, pouze z pole fatigue[].
Interpretacni logika v reportu
1. Spocitat firstHalfAvg (průměrná unava prvni poloviny segmentu) a secondHalfAvg
2. Pokud secondHalfAvg - firstHalfAvg > 10 → „Unava postupne rostla"
3. Pro každý segment: pokud segAvg[i] < segAvg[i-1] AND segAvg[i] < segAvg[i+1] → „[Segment] účastníka znovu probudil" (lokalni minimum)
4. Najdi min(segAvg) a max(segAvg) a reportovat oba s casem
5. Globalni minimum z celeho fatigue[] pole → klidkovy peak (Math.min(...fatigue.map(f => f.score)))
Barvy sloupcoveho grafu
avg < 45%#10b981 zelena — nizka unava avg 45–54%#fb923c oranzova — střední avg >= 55%#f59e0b zluta — vysoka unava
😊 Emocni mapa (Emotion Map per Segment)
😊 Emotion Distribution per Video Step Odvozena
Co se zobrazuje v reportu
Textovy popis identifikujici KTERE emoce se objevily v KTERYCH segmentech. Korelace s gesty a jinymi udalostmi.
Vzorec
function emotionsPerSegment(emotions, videoSteps) { return videoSteps.map(step => { const segEmotions = emotions .filter(e => e.timestamp >= step.startedAt && e.timestamp <= (step.endedAt || step.startedAt + 60000)) .map(e => e.emotion); const counts = {}; segEmotions.forEach(em => counts[em] = (counts[em] || 0) + 1); return { videoId: step.videoId, emotions: counts, total: segEmotions.length }; }); }
Interpretacni logika v reportu
1. Pro každý ne-Focused emoci najdi segmenty kde se vyskytuje → „Emoce [X] se objevila vyhradne během [segment]"
2. Pokud gesto (napr. Thumbs Up) ma timestamp v rozsahu stejneho segmentu → „To presne koreluje s gestem [Y]"
3. Pokud vsechny segmenty = 100% Focused → „Cela session probehla v rezimu Focused — stabilni soustredi bez emocnich vykyvu"
4. Dominantni emoce = ta s nejvyšším celkovym poctem
5. „Pozitivni emoce" = Happy, Surprised; „Neutralni" = Focused, Neutral, Relaxed; „Negativni" = None (zatim nepodporovano)
Korelace s gesty
Pro každé gesto z gestures[] (filtr gesture != 'None') najit casove prekryti se segmenty:
gestureSegment = videoSteps.find(s => gesture.timestamp >= s.startedAt && gesture.timestamp <= s.endedAt)
Pokud segment s gestem = segment s ne-Focused emoci → silna korelace, uvest v reportu.
🎙 Interakce a reakce (Interaction Analysis)
🎙 Reaction Times & Voice Interaction Quality Odvozena
Co se zobrazuje v reportu
Rozpis hlasovych odpovědi (tip), jejich reakční casy, a analyza konzistence odpovědi. U closing choice analyza formulace.
Zdrojova data
reactionTimes[]{questionId, time_ms} — cas od zobrazení vstupu do odpovědi voiceComments[]{timestamp, text, confidence} — rozpoznany hlas closingChoice{detectedKeyword, attempts, detailViewed}
Odvozené metriky
avgReactionTimeavg(reactionTimes.time_ms) reactionTimeVariancemax(time_ms) - min(time_ms) consistencyScorePokud variance < 1000ms → „konzistentni premysleni"
Interpretacni logika v reportu
1. reactionTimeVariance < 1000 → „Velmi podobne reakční casy ukazuji na konzistentni a aktivni premysleni"
2. avgReactionTime < 2000 → „Rychle odpovědi — mozna nahodile hadani nebo jistota"
3. avgReactionTime 3000–7000 → „Aktivni premysleni pred odpovědi"
4. avgReactionTime > 10000 → „Dlouhe premysleni — slozita otazka nebo nerozhodnost"
5. closingChoice.detectedKeyword != standardni slova (biometrics/hardware/adaptivity/no) → prirozena formulace (napr. „chci do reportu" místo „ne") → poznamka o porozumeni kontextu
6. closingChoice.attempts > 0 → „[N]x upozorneni pred spravnou detekci"
📏 Fyzicke chovani (Physical Behavior Summary)
📏 Distance + Head + Blink Combined Interpretation Odvozena
Co se zobrazuje v reportu
Souhrn fyzickych metrik: vzdalenost (rozsah, drift), pohyb hlavy (průměr pitch/yaw), blink rate vs norma. Kombinovana interpretace.
Zdrojova data a výpočty
Vzdalenost rozsahmin(distance.cm)max(distance.cm) Vzdalenost driftavg(posledních 10) - avg(prvních 10) Průměrný pitchavg(|headPose.pitch|) ve stupnich Průměrný yawavg(|headPose.yaw|) ve stupnich Finalni blink rateblinkRate[last].rate Blink norma15–20/min (Bentivoglio et al., 1997)
Interpretacni logika v reportu
1. Vzdalenost: drift > 0 → „Mirny trend oddalovani — prirozeny projev postupne relaxace"
drift < -5 → „Priblizovani k obrazovce — mozna unava oci nebo snaha lepe vidět"
|drift| < 2 → „Stabilni pozice po celou dobu"

2. Pohyb hlavy: avg_pitch < 3 AND avg_yaw < 5 → „Minimalni pohyb hlavy — vysoka vizualni fixace"
avg_pitch > 5 OR avg_yaw > 8 → „Zvyseny pohyb — mozna nepohodli nebo rozptyleni"

3. Blink rate: rate < 10 → „Nižší nez norma — v kombinaci se stabilni hlavou ukazuje na vysokou vizualni fixaci"
rate < 10 AND avg_pitch < 3 → „Kombinace nizkeho mrkání a stabilni hlavy = silny signal soustredi"
Poznamka: „Nízké mrkání může byt castecne artefakt detekce"
📊 Celkový profil a doporučení (Overall Profile & Content Recommendations)
📊 Participant Type + Content Effectiveness Score Odvozena
Co se zobrazuje v reportu
Tri odstavce: (1) Typ účastníka, (2) Zajimavost — nejzajimavejsi vs nejmene poutavy segment, (3) Doporučení pro obsah.
Typ účastníka — rozhodovaci strom
function classifyParticipant(data) { const gazeAvg = avg(data.gazeAttention, 'score'); const fatAvg = avg(data.fatigue, 'score'); const headMov = avgHeadMovement(data.headPose); const gestures = data.gestures.filter(g => g.gesture !== 'None').length; const voices = data.voiceComments.length; const drops = detectDropsAndSpikes(data.gazeAttention); if (gazeAvg > 60 && fatAvg < 60 && headMov < 1.0) return 'Soustředěný, klidný, analyticky pozorovatel'; if (gazeAvg > 60 && (gestures > 0 || voices > 0)) return 'Aktivní, zapojený účastník'; if (fatAvg > 60) return 'Unaveny, se snizenymi vysledky'; if (drops.drops.length > 5 && drops.recoveryRatio < 0.5) return 'Rozptylovaný, s castymi vypadky pozornosti'; if (gazeAvg < 50 && gestures === 0 && voices === 0) return 'Pasivní pozorovatel'; return 'Standardni účastník'; }
Zajimavost — identifikace extremu
1. Spocitat per-segment: gazeAvg, emotionVariety (počet unikatnich emoci != Focused), gestureCount, fatigueAvg
2. Segment s max(gazeAvg) AND max(emotionVariety) AND max(gestureCount) → „nejzajimavejsi"
3. Segment s min(gazeAvg) AND max(fatigueAvg) → „nejmene poutavy"
4. Textova sablona: „[Název max segmentu] vyvolal nejsilnejsi reakci — nejvyšší pozornost, [specifika]. [Název min segmentu] byl naopak nejméně poutavy."
Doporučení pro obsah — content effectiveness
segmentScore = gazeAvg × (1 - fatigueAvg / 100)

Logika:
• Segment s min(segmentScore) → „Zvazit zkraceni nebo oziveni [segmentu]"
• Segment s max(segmentScore) → „[Segment] funguje jako hlavni hook scénáře"
• Pokud max(segmentScore) / min(segmentScore) > 1.5 → „Vyrazny rozdil v efektivite mezi segmenty"
• Pokud max(segmentScore) / min(segmentScore) < 1.2 → „Segmenty jsou vyrovnane"
Tabulka sablon pro textovy vystup
Typ„Typ: [classifyParticipant result]. [Popis]" Zajimavost„[max segment] = nejzajimavejsi. [min segment] = nejmene poutavy." Doporučení„Zvazit [akci] u [min segment]. [max segment] funguje jako hook."
Poznámka
Vsechny textove vystupy v reportu jsou generovany z techto sablon a ciselnych prahu. Nejde o volny text — kazda veta ma jasny datovy podklad. Pokud data nedosahnou prahu, prislusna veta se v reportu nezobrazí.
💪 Svalová analýza obličeje (FACS — Facial Action Coding System)
🔬 FACS Blendshape Aggregation Odvozena
Účel
Z 52 ARKit blendshapes extrahovat vzory odpovídající známým výrazům obličeje podle FACS (Ekman & Friesen, 1978). ARKit blendshapes mapuji na FACS Action Units (AU).
Vědecký základ
Ekman & Friesen (1978)Facial Action Coding System (FACS). Standardní systém pro popis pohybů obličejových svalů.

ARKit → FACS mapovani (Ozel, 2022, facethefacs.com):
browDownLeft/Right → AU4 (Brow Lowerer) — corrugator supercilii
eyeSquintLeft/Right → AU7 (Lid Tightener) — orbicularis oculi, palpebral
cheekSquintLeft/Right → AU6 (Cheek Raiser) — orbicularis oculi, orbital
mouthSmileLeft/Right → AU12 (Lip Corner Puller) — zygomaticus major
mouthFrownLeft/Right → AU15 (Lip Corner Depressor) — depressor anguli oris
mouthPressLeft/Right → AU24 (Lip Pressor) — orbicularis oris
jawOpen → AU26/27 (Jaw Drop) — masseter; internal pterygoid
Klíčové vzory (patterns)
Soustředění / ConcentrationAU4 (browDown > 0.3) + AU7 (eyeSquint > 0.2) Radost / HappinessAU6 (cheekSquint > 0.2) + AU12 (mouthSmile > 0.3) Přemýšlení / DeliberationAU24 (mouthPress > 0.1) + AU4 (browDown > 0.3) Překvapení / SurpriseAU5 (eyeWide > 0.3) + AU26 (jawOpen > 0.2) Znechucení / DispleasureAU15 (mouthFrown > 0.2) + AU4 (browDown > 0.4)
Výpočet per segment
function facsAggregation(blendshapeLog, startTs, endTs) { const seg = blendshapeLog.filter(b => b.timestamp >= startTs && b.timestamp <= endTs); if (seg.length === 0) return null; const keys = ['browDownLeft','browDownRight','eyeSquintLeft','eyeSquintRight', 'mouthSmileLeft','mouthSmileRight','mouthPressLeft','mouthPressRight', 'cheekSquintLeft','cheekSquintRight','jawOpen','eyeWideLeft','eyeWideRight']; const result = {}; keys.forEach(k => { const vals = seg.map(s => s.values[k] || 0); result[k] = { avg: vals.reduce((a,v) => a+v, 0) / vals.length, max: Math.max(...vals) }; }); return result; }
Poznámky
Blendshape values jsou 0.0–1.0. Průměrování přes segment dává „bazální tonus" svalu. Maximum ukazuje peak intenzitu. Pro identifikaci krátkých úsměvů stačí max(mouthSmile) > 0.5 i když avg < 0.05.
📈 Variabilita pozornosti (Gaze Attention Variability)
📊 Gaze Std per Segment Odvozena
Účel
Průměr pozornosti ukazuje „kolik", ale směrodatná odchylka ukazuje „jak dynamicky" — vyšší std = účastník aktivně přesouvá pozornost = hlubší kognitivní zpracování. Nízká std = monotónní sledování.
Vzorec
std = sqrt( Σ(xi - mean)² / n )
Kde xi = jednotlivé gaze scores v segmentu, mean = průměr segmentu, n = počet vzorků.
Interpretace
std > 8Aktivní zpracování — dynamické přesuny pozornosti std 5–8Normální sledování std < 5Fixovaný pohled — buď vysoké soustředí, nebo monotónie
Implementace
function gazeStd(gazeAttention, startTs, endTs) { const scores = gazeAttention .filter(g => g.timestamp >= startTs && g.timestamp <= endTs) .map(g => g.score); if (scores.length < 2) return 0; const mean = scores.reduce((a,v) => a+v, 0) / scores.length; const variance = scores.reduce((a,v) => a + (v - mean) ** 2, 0) / scores.length; return Math.sqrt(variance); }
Poznámka
Rozlišovat nízkou std ze soustředění (= vysoká avg + nízká std) vs. nizkou std z nezájmu (= nízká avg + nízká std). Vždy reportovat std společně s průměrem.
🔄 Analýza pohybů hlavy (Head Movement Events)
🔄 Head Movement per Segment + Spike Detection Odvozena
Metrika 1: Průměrný pohyb per segment
avg_movement = Σ(|pitch[i]-pitch[i-1]| + |yaw[i]-yaw[i-1]|) / (2 × (n-1))
Měří frame-to-frame „jitter" — kolik se hlava pohne průměrně každý snímek.
Metrika 2: Spike detection
spike = |value[i] - value[i-1]| > threshold
Prah pro yaw: 10°, prah pro pitch: . Spiky indikují prudké otočení hlavy — reakce na externí podnět, změna pozice, nebo překvapení.
Interpretace
avg_movement < 0.3°Velmi stabilní — fixovaný pohled na obsah avg_movement 0.3–1.0°Normální — mírne pohyby pri zpracovani avg_movement > 1.0°Zvýšená aktivita — fyzická reakce na obsah nebo rozptýlení Yaw spike > 10°Prudké otočení — externí podnět nebo změna pozice
Implementace
function headMovementPerSegment(headPose, startTs, endTs) { const seg = headPose.filter(h => h.timestamp >= startTs && h.timestamp <= endTs); if (seg.length < 2) return { avgMovement: 0, spikes: [] }; let totalDiff = 0, spikes = []; for (let i = 1; i < seg.length; i++) { const dP = Math.abs(seg[i].pitch - seg[i-1].pitch); const dY = Math.abs(seg[i].yaw - seg[i-1].yaw); totalDiff += dP + dY; if (dY > 10 || dP > 5) spikes.push({ ts: seg[i].timestamp, dP, dY }); } return { avgMovement: totalDiff / (2 * (seg.length - 1)), spikes }; }
📊 Korelace: únava ↔ pozornost
📉 Pearson Correlation: Fatigue vs Gaze Odvozena
Účel
Ověřit, zda únava a pozornost měří konzistentní jev. Očekávaná negativní korelace (vyšší únava → nižší pozornost) validuje obě metriky.
Vzorec (Pearson r)
r = Σ((xi - x̄)(yi - ȳ)) / sqrt(Σ(xi - x̄)² × Σ(yi - ȳ)²)
Kde xi = gaze score, yi = fatigue score, párováno podle nejbližšího timestampu (tolerance 2s).
Interpretace
r = -0.7 az -1.0Silná negativní — únava dominantně ovlivňuje pozornost r = -0.3 az -0.7Střední negativní — únava je jeden z faktorů r = 0 az -0.3Slabá — pozornost řídí především obsah, ne únava r > 0Neočekávaná — možná chyba v datech nebo netypický účastník
Implementace
function pearsonCorrelation(gazeAttention, fatigue) { const pairs = []; gazeAttention.forEach(g => { const closest = fatigue.reduce((best, f) => Math.abs(f.timestamp - g.timestamp) < Math.abs(best.timestamp - g.timestamp) ? f : best); if (Math.abs(closest.timestamp - g.timestamp) < 2000) pairs.push([g.score, closest.score]); }); const n = pairs.length; if (n < 3) return null; const mG = pairs.reduce((a,[g]) => a+g, 0)/n; const mF = pairs.reduce((a,[,f]) => a+f, 0)/n; const cov = pairs.reduce((a,[g,f]) => a + (g-mG)*(f-mF), 0)/n; const sG = Math.sqrt(pairs.reduce((a,[g]) => a + (g-mG)**2, 0)/n); const sF = Math.sqrt(pairs.reduce((a,[,f]) => a + (f-mF)**2, 0)/n); return (sG > 0 && sF > 0) ? cov / (sG * sF) : 0; }
⚡ Detekce rozptýlení a návratu (Attention Drops & Spikes)
Gaze Attention Drops & Recovery Odvozena
Účel
Identifikovat přesné momenty kde účastník ztratil pozornost (drop) a kde se vrátil (spike). Umožňuje korelaci s externími událostmi (přechod segmentu, zvuk, pohyb).
Vzorec
drop = score[i-1] - score[i] > threshold
spike = score[i] - score[i-1] > threshold
Výchozí práh: 15 bodu. Nastavitelný per scénář.
Odvozené metriky
drop_countCelkový počet poklesů > threshold spike_countCelkový počet návratů > threshold max_dropNejvětší jednorázový pokles recovery_ratiospike_count / drop_count — blize k 1.0 = dobrá samoregulace avg_recovery_timePrůměrný čas (s) mezi drop a následným spike
Implementace
function detectDropsAndSpikes(gazeAttention, threshold = 15) { const drops = [], spikes = []; for (let i = 1; i < gazeAttention.length; i++) { const diff = gazeAttention[i].score - gazeAttention[i-1].score; if (diff < -threshold) drops.push({ ts: gazeAttention[i].timestamp, from: gazeAttention[i-1].score, to: gazeAttention[i].score }); if (diff > threshold) spikes.push({ ts: gazeAttention[i].timestamp, from: gazeAttention[i-1].score, to: gazeAttention[i].score }); } const recoveryRatio = drops.length > 0 ? spikes.length / drops.length : 1; return { drops, spikes, recoveryRatio }; }
📐 Ergonomie a vzdálenost (Distance Ergonomics)
📐 Distance Drift & Ergonomic Zone Odvozena
Účel
Vyhodnotit, zda účastník sedí v ergonomické zóně a jak se jeho vzdálenost mění v čase (drift = relaxace vs. naklonění vpřed = snaha lépe vidět).
Vědecký základ
CCOHS (Canadian Centre for Occupational Health and Safety) — Resting Point of Accommodation (RPA) ≈ 80 cm. Při této vzdálenosti oční svaly nepotřebují úsilí na zaostření.

OSHA eTools Computer Workstations — doporučený rozsah 50–100 cm (20–40 palcu) od očí k monitoru.

Ergonomická interpretace driftu: postupné oddalování = relaxace, přibližování = snaha lépe vidět (možná únava očí nebo malý text).
Metriky
avg_distanceavg(distance.cm) in_ergonomic_zone50 <= avg <= 100 → boolean driftavg(last 10 samples) - avg(first 10 samples) drift_directiondrift > 0 = oddalování (relaxace), drift < 0 = přibližování near_rpa|avg - 80| < 10 → v zóně klidového zaostření
🎤 Hlasová aktivita a analýza mikrofonu
🎤 Voice Activity & Ambient Analysis Odvozena
Účel
Odlišit aktivní řeč od okolního šumu. Identifikovat hlasový profil účastníka.
Metriky z micVolume[]
speaking_ratiocount(isSpeaking=true) / total ambient_volumeavg(rms) where isSpeaking=false speech_volumeavg(rms) where isSpeaking=true snr (signal-to-noise)speech_volume / ambient_volume dominant_freq_speechavg(dominantFreq) where isSpeaking=true
Interpretace frekvence
85–180 HzTypicky mužský hlas (fundamental) 165–255 HzTypicky ženský hlas (fundamental) 500–1000 HzHarmonics — detekce harmonických složek řeči 0 HzŽádná dominantní frekvence — ticho nebo šum
Poznámka ke kalibraci
V demo session byl speaking detector nekonzistentní (detekoval 2 ze 3 skutečných hlasových vstupů). Doporučení: porovnat voiceComments[] (speech-to-text výsledky) s micVolume[].isSpeaking a kalibrovat práh speaking detektoru.
👤 Profil účastníka (Participant Profile Generation)
👤 Behavioral Profile Odvozena
Účel
Automaticky generovaný textový profil účastníka na základě kombinace všech metrik. Určen pro trenéry a manažery školení.
Rozhodovací logika
Typ: Soustředěný, klidnýgazeAvg > 60 AND fatigueAvg < 60 AND headMovement < 1.0° Typ: Aktivní, zapojenýgazeAvg > 60 AND gestureCount > 0 AND voiceComments > 0 Typ: Unavený, klesajícífatigueAvg > 60 AND gazeTrend < 0 (klesající trend) Typ: RozptylovanýdropCount > 5 AND recoveryRatio < 0.5 Typ: PasivnígazeAvg < 50 AND gestureCount = 0 AND voiceComments = 0
Doporučení pro obsah (per segment)
Pro každý video segment spočítat segmentScore = gazeAvg × (1 - fatigueAvg/100). Segmenty s nejnižším skóre → kandidáti na zkrácení nebo přepracování. Segmenty s nejvyšším skóre → potvrdit jako efektivní.
Příklad segmentového skóre
Pilir 1: Biometrika76.2 × (1 - 39.8/100) = 45.9 ← nejvyšší Uvod74.2 × (1 - 34.9/100) = 48.3 ← také vysoké Pilir 2: Hardware60.9 × (1 - 55.0/100) = 27.4 ← nejnižší Pilir 3: Adaptivita68.3 × (1 - 48.6/100) = 35.1 ← střední
— Konec dokumentace —
BEAM X1 v3 · Kapitola 1: Sběrové metriky · Kapitola 2: Kontextuální analýza · Duben 2026