lct-hack/backend/tests/test_reference.py
Ivan Gerasimov fadfa3e479 lct-16 и половина lct-19: разбор, отчёт, внешний монитор, эталон
Эталонный диалог собирается кодом из фактов и чек-листа: написанный
руками, он разошёлся бы с фактами при первой же правке сценария,
и курсанта оштрафовали бы за правильный ответ.

Отчёт: метрики фактом против норматива со ссылкой, отметка E1 на каждый
недобытый факт с эталонным вопросом, расхождение самооценки — что
курсант заметил сам, чего не заметил, что отметил зря. Не заметил —
самое ценное для разбора.

Внешний монитор — не отдельное приложение, а другой режим отрисовки тех
же событий: крупный таймер опроса, ход разговора, карточка, после оценки
разбор на весь экран.

Коррекция преподавателем сохраняет автооценку рядом: видно, что
скорректировано и кем.

Найдено: все метрики весили одинаково, и курсант, не задавший ни одного
вопроса, но заполнивший карточку руками, получал 87 из 100. Предварительные
веса (полнота опроса — 4) дают 74; окончательные утверждает методист,
вопрос записан в DEBRIEF.md.
2026-09-17 21:32:12 +03:00

48 lines
1.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Эталонный диалог собирается из фактов, а не пишется руками."""
from pathlib import Path
import pytest
from app.scenarios.loader import load_file
from app.scoring.reference import build, missed_steps
LIBRARY = Path(__file__).resolve().parents[2] / "scenarios"
@pytest.fixture(scope="module")
def scenario():
return load_file(LIBRARY / "fire-apartment-l2.yaml", LIBRARY)
def test_every_checklist_question_becomes_a_step(scenario):
reference = build(scenario)
assert [step.checklist_id for step in reference.steps] == [
item.id for item in scenario.checklist if item.question
]
assert reference.first_line == scenario.first_line
def test_answers_come_from_facts_not_from_prose(scenario):
"""Эталон не может разойтись с фактами: ответы взяты из них же."""
reference = build(scenario)
address = next(step for step in reference.steps if step.fact_id == "f_address")
fact = next(fact for fact in scenario.facts if fact.id == "f_address")
assert fact.value in address.answer
def test_step_without_a_fact_has_no_answer(scenario):
"""«Представьтесь» факта не добывает — и ответа в эталоне у него нет."""
caller = next(step for step in build(scenario).steps if step.checklist_id == "q_caller")
assert caller.fact_id is None and caller.answer is None
assert not caller.required
def test_missed_steps_are_only_the_required_ones(scenario):
missed = missed_steps(scenario, revealed=["f_address"])
assert [step.fact_id for step in missed] == ["f_what_burns", "f_people", "f_smoke", "f_gas"]
assert all(step.question for step in missed), "в разборе нужен текст эталонного вопроса"
def test_without_slot_machine_nothing_is_declared_missed(scenario):
assert missed_steps(scenario, revealed=None) == []