lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
"""Сборка отчёта сессии — единицы истории.
|
|
|
|
|
|
|
|
|
|
|
|
Из отчётов складываются профиль курсанта, дельта попыток и аналитика группы.
|
|
|
|
|
|
Ни одной отметки без обоснования: у каждой есть код, факт и норматив
|
|
|
|
|
|
(docs/product/DEBRIEF.md).
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from uuid import UUID
|
|
|
|
|
|
|
|
|
|
|
|
from app.domain.events import (
|
2026-09-26 17:12:29 +00:00
|
|
|
|
AICoaching,
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
CompetencyScore,
|
2026-09-21 19:56:13 +03:00
|
|
|
|
DdsCardReport,
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
HintShown,
|
|
|
|
|
|
HintUsage,
|
|
|
|
|
|
InstructorNoteShown,
|
|
|
|
|
|
Metric,
|
|
|
|
|
|
SelfAssessment,
|
|
|
|
|
|
SelfAssessmentDiff,
|
|
|
|
|
|
SessionReport,
|
2026-09-21 17:40:54 +03:00
|
|
|
|
Speaker,
|
|
|
|
|
|
Exercise,
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
)
|
|
|
|
|
|
from app.domain.taxonomy import Finding
|
|
|
|
|
|
from app.scenarios.schema import Scenario
|
|
|
|
|
|
from app.scoring.reference import build as build_reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def assess_difference(
|
|
|
|
|
|
scenario: Scenario, revealed: list[str] | None, missed_claimed: list[str]
|
|
|
|
|
|
) -> SelfAssessmentDiff:
|
|
|
|
|
|
"""Сверить, что курсант считает пропущенным, с тем, что он пропустил на деле."""
|
|
|
|
|
|
reference = build_reference(scenario)
|
|
|
|
|
|
required = {step.checklist_id: step.fact_id for step in reference.steps if step.required}
|
|
|
|
|
|
really_missed = {
|
|
|
|
|
|
checklist_id
|
|
|
|
|
|
for checklist_id, fact_id in required.items()
|
|
|
|
|
|
if revealed is not None and fact_id not in revealed
|
|
|
|
|
|
}
|
|
|
|
|
|
claimed = set(missed_claimed)
|
|
|
|
|
|
return SelfAssessmentDiff(
|
|
|
|
|
|
noticed=sorted(claimed & really_missed),
|
|
|
|
|
|
unnoticed=sorted(really_missed - claimed),
|
|
|
|
|
|
overcautious=sorted(claimed - really_missed),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build(session_id: UUID, state, scenario: Scenario) -> SessionReport:
|
|
|
|
|
|
score = state.score or {}
|
2026-09-24 01:10:49 +03:00
|
|
|
|
metrics = [Metric.model_validate(item) for item in score.get("metrics", [])]
|
|
|
|
|
|
failed_metrics = sum(not item.passed and item.weight > 0 for item in metrics)
|
2026-09-21 17:40:54 +03:00
|
|
|
|
# Без модели эмбеддингов распознать *какие* факты прозвучали нельзя.
|
|
|
|
|
|
# Но если оператор не произнёс ни слова, все обязательные вопросы точно
|
|
|
|
|
|
# пропущены — это доказуемый частный случай и он нужен офлайн-разбору.
|
|
|
|
|
|
revealed = ([fact.id for fact in state.slots.revealed_facts()] if state.slots else
|
|
|
|
|
|
[] if not any(entry.speaker is Speaker.OPERATOR for entry in state.transcript)
|
|
|
|
|
|
else None)
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
reference = build_reference(scenario)
|
|
|
|
|
|
|
|
|
|
|
|
self_assessment = None
|
|
|
|
|
|
difference = None
|
|
|
|
|
|
if state.self_assessment is not None:
|
|
|
|
|
|
self_assessment = SelfAssessment(
|
|
|
|
|
|
missed=state.self_assessment["missed"],
|
|
|
|
|
|
comment=state.self_assessment.get("comment", ""),
|
|
|
|
|
|
submitted_at=state.ended_at or state.transcript[-1].at,
|
|
|
|
|
|
)
|
|
|
|
|
|
difference = assess_difference(scenario, revealed, self_assessment.missed)
|
|
|
|
|
|
|
|
|
|
|
|
questions = {step.checklist_id: step.question for step in reference.steps}
|
|
|
|
|
|
missed_checklist = [
|
|
|
|
|
|
step.checklist_id
|
|
|
|
|
|
for step in reference.steps
|
|
|
|
|
|
if step.required and revealed is not None and step.fact_id not in revealed
|
2026-09-21 19:56:13 +03:00
|
|
|
|
] if state.exercise is Exercise.CALL else []
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
|
|
|
|
|
|
return SessionReport(
|
|
|
|
|
|
session_id=session_id,
|
2026-09-26 21:38:09 +00:00
|
|
|
|
scenario_id=(state.desk.scenarios[0].id
|
|
|
|
|
|
if state.exercise is Exercise.DDS and state.desk.scenarios
|
2026-09-21 19:56:13 +03:00
|
|
|
|
else scenario.id),
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
mode=state.mode,
|
2026-09-26 18:12:27 +03:00
|
|
|
|
exercise=state.exercise,
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
attempt=state.attempt,
|
2026-09-24 01:10:49 +03:00
|
|
|
|
criteria=state.criteria,
|
|
|
|
|
|
failed_metrics=failed_metrics,
|
|
|
|
|
|
passed=failed_metrics <= state.criteria.allowed_errors,
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
transcript=list(state.transcript),
|
|
|
|
|
|
findings=[Finding.model_validate(item) for item in score.get("findings", [])],
|
2026-09-24 01:10:49 +03:00
|
|
|
|
metrics=metrics,
|
2026-09-21 19:56:13 +03:00
|
|
|
|
card_results=[DdsCardReport.model_validate(item)
|
|
|
|
|
|
for item in score.get("card_results", [])],
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
competencies=[CompetencyScore.model_validate(item) for item in score.get("competencies", [])],
|
|
|
|
|
|
# Эталонные вопросы по шагам: по каждому пропущенному пункту видно,
|
|
|
|
|
|
# какой вопрос был правильным.
|
|
|
|
|
|
reference_questions=[
|
|
|
|
|
|
HintShown(checklist_id=step.checklist_id, question=step.question)
|
|
|
|
|
|
for step in reference.steps
|
2026-09-21 19:56:13 +03:00
|
|
|
|
] if state.exercise is Exercise.CALL else [],
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
missed_checklist=missed_checklist,
|
|
|
|
|
|
hints_used=[
|
|
|
|
|
|
HintUsage(checklist_id=checklist_id, question=questions.get(checklist_id, ""), at=at)
|
|
|
|
|
|
for checklist_id, at in state.hints_log
|
|
|
|
|
|
],
|
|
|
|
|
|
self_assessment=self_assessment,
|
|
|
|
|
|
self_assessment_diff=difference,
|
|
|
|
|
|
notes=[InstructorNoteShown.model_validate(note) for note in state.notes],
|
|
|
|
|
|
score_auto=score.get("score_auto", 0.0),
|
2026-09-27 16:13:21 +03:00
|
|
|
|
score_reviewed=score.get("score_reviewed"),
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
score_final=score.get("score_final", score.get("score_auto", 0.0)),
|
|
|
|
|
|
overridden_by=score.get("overridden_by"),
|
|
|
|
|
|
override_comment=score.get("override_comment"),
|
2026-09-26 17:12:29 +00:00
|
|
|
|
ai_coaching=(AICoaching.model_validate(score["ai_coaching"])
|
|
|
|
|
|
if score.get("ai_coaching") else None),
|
2026-09-27 16:35:13 +03:00
|
|
|
|
total_paused_ms=state.total_paused_ms,
|
lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.
Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.
Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.
Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.
Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00
|
|
|
|
)
|