lct-hack/backend/app/scoring/reference.py
2026-09-26 17:13:45 +00:00

69 lines
2.8 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.

"""Эталонный диалог: как должен был пройти опрос.
Собирается **кодом** из чек-листа и фактов сценария. Писать его руками нельзя:
методист поправит факт и забудет эталон, и курсант получит штраф за правильный
ответ (docs/spec/SCENARIO-FORMAT.md).
Работает в двух местах: в разборе — «вот какой вопрос стоял на этом шаге»
(docs/product/DEBRIEF.md), и в эталон-плеере на внешнем мониторе, когда
появится предгенерация озвучки (lct-19, требует ключа LLM).
"""
from pydantic import BaseModel
from app.dialog.caller import REVEAL
from app.dialog.persona import BASE_MOOD
from app.domain.events import Mood
from app.scenarios.schema import Scenario
class ReferenceStep(BaseModel):
"""Шаг эталонного опроса: вопрос оператора и ответ звонящего на него."""
checklist_id: str
question: str
fact_id: str | None = None
answer: str | None = None
required: bool = False
hidden: bool = False
class ReferenceDialog(BaseModel):
scenario_id: str
first_line: str
steps: list[ReferenceStep]
def build(scenario: Scenario) -> ReferenceDialog:
facts = {fact.id: fact for fact in scenario.facts}
required = set(scenario.ground_truth.required_facts)
mood = BASE_MOOD.get(scenario.persona.base, Mood.CALM)
steps: list[ReferenceStep] = []
for item in scenario.checklist:
if not item.question:
continue
fact = facts.get(item.fact) if item.fact else None
steps.append(
ReferenceStep(
checklist_id=item.id,
question=item.question,
fact_id=fact.id if fact else None,
# Ответ звонящего — тем же шаблоном, каким он отвечает в живом
# звонке: эталон должен звучать так же, а не литературно.
answer=REVEAL[mood].format(fact=fact.value) if fact else None,
required=bool(fact and fact.id in required),
)
)
return ReferenceDialog(scenario_id=scenario.id, first_line=scenario.first_line, steps=steps)
def missed_steps(scenario: Scenario, revealed: list[str] | None) -> list[ReferenceStep]:
"""Шаги, которые курсант не отработал. `None` — слот-автомата не было."""
if revealed is None:
return []
return [
step
for step in build(scenario).steps
if step.fact_id and step.fact_id not in revealed and step.required
]