lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный руками, он разошёлся бы с фактами при первой же правке сценария, и курсанта оштрафовали бы за правильный ответ. Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый недобытый факт с эталонным вопросом, расхождение самооценки — что курсант заметил сам, чего не заметил, что отметил зря. Не заметил — самое ценное для разбора. Внешний монитор — не отдельное приложение, а другой режим отрисовки тех же событий: крупный таймер опроса, ход разговора, карточка, после оценки разбор на весь экран. Коррекция преподавателем сохраняет автооценку рядом: видно, что скорректировано и кем. Найдено: все метрики весили одинаково, и курсант, не задавший ни одного вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные веса (полнота опроса — 4) дают 74; окончательные утверждает методист, вопрос записан в DEBRIEF.md.
This commit is contained in:
parent
25143645d3
commit
fadfa3e479
20 changed files with 781 additions and 9 deletions
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { KioCard } from "@/features/kio-card/KioCard";
|
||||
import { ModeBanner, hintsAllowed } from "@/features/mode-banner/ModeBanner";
|
||||
import { Debrief } from "@/features/debrief/Debrief";
|
||||
import { SelfAssessment } from "@/features/self-assessment/SelfAssessment";
|
||||
import { InterviewTimer, Timers } from "@/features/timers/Timers";
|
||||
import { display } from "@/features/kio-card/merge";
|
||||
|
|
@ -58,9 +59,8 @@ export function Call() {
|
|||
{call.phase === "ended" && !call.selfAssessed && call.checklist.length > 0 && (
|
||||
<SelfAssessment checklist={call.checklist} onSubmit={call.submitSelfAssessment} />
|
||||
)}
|
||||
{call.selfAssessed && (
|
||||
<p>{call.scoreReady ? "Оценка готова — разбор открывает преподаватель." : "Самооценка принята, оценка считается…"}</p>
|
||||
)}
|
||||
{call.selfAssessed && !call.report && <p>Самооценка принята, оценка считается…</p>}
|
||||
{call.report && <Debrief report={call.report} />}
|
||||
|
||||
<div className="columns">
|
||||
<section>
|
||||
|
|
|
|||
74
frontend/src/pages/wall/Wall.tsx
Normal file
74
frontend/src/pages/wall/Wall.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// Внешний монитор: группа смотрит, как идёт занятие, и разбирает вместе с ним.
|
||||
//
|
||||
// Это не отдельное приложение, а другой режим отрисовки тех же событий: крупный
|
||||
// шрифт, высокий контраст, минимум деталей. Никакого взаимодействия — экран висит
|
||||
// и показывает (docs/arch/FRONTEND.md).
|
||||
|
||||
import { Debrief } from "@/features/debrief/Debrief";
|
||||
import { KioCard } from "@/features/kio-card/KioCard";
|
||||
import { ModeBanner } from "@/features/mode-banner/ModeBanner";
|
||||
import { InterviewTimer } from "@/features/timers/Timers";
|
||||
import { useObserve } from "@/features/observe/useObserve";
|
||||
import { sessionIdFromUrl } from "@/shared/api/session";
|
||||
|
||||
export function Wall() {
|
||||
const sessionId = sessionIdFromUrl();
|
||||
const session = useObserve(sessionId);
|
||||
|
||||
if (!sessionId) {
|
||||
return (
|
||||
<main className="page wall">
|
||||
<h1>Внешний монитор</h1>
|
||||
<p className="warn">Откройте ссылку вида /wall?session=…</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (session.report) {
|
||||
return (
|
||||
<main className="page wall">
|
||||
<ModeBanner mode={session.mode} />
|
||||
<h1>{session.title} · разбор</h1>
|
||||
<Debrief report={session.report} big />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="page wall">
|
||||
<ModeBanner mode={session.mode} />
|
||||
<h1>
|
||||
{session.title ?? "Ожидание занятия"}
|
||||
{session.trainee && ` · ${session.trainee}`}
|
||||
</h1>
|
||||
<InterviewTimer timers={session.timers} big />
|
||||
|
||||
<div className="columns">
|
||||
<section>
|
||||
<h2>Ход разговора</h2>
|
||||
<table className="grid">
|
||||
<tbody>
|
||||
{session.transcript.slice(-12).map((line) => (
|
||||
<tr key={line.ref}>
|
||||
<th>{line.speaker === "caller" ? "звонящий" : "оператор"}</th>
|
||||
<td>
|
||||
{line.text}
|
||||
{session.notes.has(line.ref) && (
|
||||
<div className="ref">пометка: {session.notes.get(line.ref)}</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{session.transcript.length === 0 && <tr><td colSpan={2}>—</td></tr>}
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="ref">подсказок использовано: {session.hintsUsed}</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Карточка</h2>
|
||||
<KioCard state={session.card} required={session.requiredFields} readOnly />
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue