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
|
|
@ -111,6 +111,7 @@ async def _handle(session_id: UUID, state, event) -> None:
|
|||
return
|
||||
checklist_id, question = nxt
|
||||
state.hints_shown.append(checklist_id)
|
||||
state.hints_log.append((checklist_id, now_utc()))
|
||||
shown = HintShown(checklist_id=checklist_id, question=question)
|
||||
hub.broadcast(session_id, shown)
|
||||
if hub.journal:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from pydantic import TypeAdapter, ValidationError
|
|||
|
||||
from app.domain.events import (
|
||||
CallEndReason,
|
||||
ScoreReady,
|
||||
CallIncoming,
|
||||
ErrorEvent,
|
||||
ErrorKind,
|
||||
|
|
@ -131,6 +132,14 @@ async def control(ws: WebSocket, session_id: UUID) -> None:
|
|||
case "session.stop":
|
||||
await _stop(session_id)
|
||||
case "instructor_note.add":
|
||||
state = hub.get(session_id)
|
||||
if state is not None:
|
||||
state.notes.append({
|
||||
"type": "instructor_note.shown",
|
||||
"transcript_ref": event.transcript_ref,
|
||||
"text": event.text,
|
||||
"author": "преподаватель",
|
||||
})
|
||||
hub.to_observers(
|
||||
session_id,
|
||||
InstructorNoteShown(
|
||||
|
|
@ -147,6 +156,17 @@ async def control(ws: WebSocket, session_id: UUID) -> None:
|
|||
state = hub.get(session_id)
|
||||
if state is not None:
|
||||
hub.to_observers(session_id, ReferenceStarted(scenario_id=state.scenario_id))
|
||||
case "score.override":
|
||||
state = hub.get(session_id)
|
||||
if state is not None and state.score is not None:
|
||||
# Автооценка остаётся рядом: видно, что скорректировано и кем.
|
||||
state.score = {
|
||||
**state.score,
|
||||
"score_final": float(event.verdict) if event.verdict.replace(".", "", 1).isdigit() else state.score["score_auto"],
|
||||
"overridden_by": "преподаватель",
|
||||
"override_comment": event.comment,
|
||||
}
|
||||
hub.to_observers(session_id, ScoreReady(session_id=session_id))
|
||||
case "director.inject":
|
||||
# Поведение звонящего — карточка lct-07, пульт — lct-22.
|
||||
# До них директива копится в состоянии и видна в разборе.
|
||||
|
|
|
|||
Loading…
Reference in a new issue