Complete incident selection and 112 to DDS workflow

This commit is contained in:
andreysk0304 2026-09-21 20:10:57 +03:00
commit 68dd83c7c2
21 changed files with 281 additions and 56 deletions

View file

@ -100,25 +100,37 @@ async def _handle(session_id: UUID, state, event) -> None:
code=ErrorKind.UNSUPPORTED_EVENT, message="Действие недоступно в этом упражнении",
))
return
if state.exercise is Exercise.CARD and state.dispatched_card is not None:
hub.to_trainee(session_id, ErrorEvent(
code=ErrorKind.UNSUPPORTED_EVENT,
message="Карточка уже передана в ДДС и не может быть изменена",
))
return
match event.type:
case "card.submit":
state.kio.registered_at = state.started_at or now_utc()
state.kio.response_status = ResponseStatus.TRANSFERRED
state.dispatched_card = state.kio.model_copy(deep=True)
state.dispatched_at = now_utc()
state.ended_at = state.dispatched_at
state.end_reason = CallEndReason.COMPLETE
hub.stop_ticker(session_id)
if state.handoff_to_dds:
state.on_event("dds.dispatch")
else:
state.ended_at = state.dispatched_at
state.end_reason = CallEndReason.COMPLETE
hub.stop_ticker(session_id)
hub.to_station(session_id, state.card_received_event())
hub.to_station(session_id, StationState(snapshot=state.station_snapshot()))
hub.to_observers(session_id, KioState(kio=state.kio))
hub.to_trainee(session_id, CallEnded(reason=CallEndReason.COMPLETE))
hub.to_observers(session_id, SessionEnded(reason=CallEndReason.COMPLETE))
if hub.journal:
await hub.journal.session_ended(
session_id, state.ended_at, CallEndReason.COMPLETE.value
)
await finish(session_id, state)
if state.handoff_to_dds:
hub.to_observers(session_id, state.snapshot())
else:
hub.to_observers(session_id, SessionEnded(reason=CallEndReason.COMPLETE))
if hub.journal:
await hub.journal.session_ended(
session_id, state.ended_at, CallEndReason.COMPLETE.value
)
await finish(session_id, state)
case "call.answer":
state.on_event("call.answer")
state.started_at = now_utc()
@ -295,7 +307,7 @@ async def call(ws: WebSocket, session_id: UUID) -> None:
from app.api.ws.control import card_briefing
hub.to_trainee(session_id, card_briefing(state))
if state.ended:
if state.ended or state.dispatched_card is not None:
hub.to_trainee(session_id, CallEnded(reason=state.end_reason or CallEndReason.COMPLETE))
if state.score is not None:
hub.to_trainee(session_id, ScoreReady(session_id=session_id))

View file

@ -72,10 +72,17 @@ def card_briefing(state: SessionState) -> CardBriefing:
required_fields=([field for field in state.required_fields if field != "dds"]
if scenario.ground_truth.incident_code else list(state.required_fields)),
card=state.kio,
handoff_to_dds=state.handoff_to_dds,
)
async def _start(session_id: UUID, event, who=None) -> None:
if event.handoff_to_dds and event.exercise is not Exercise.CARD:
hub.to_observers(session_id, ErrorEvent(
code=ErrorKind.SCENARIO_INVALID,
message="Передача в ДДС доступна только для текстовой карточки 112",
))
return
scenario = store.get(event.scenario_id)
if scenario is None:
hub.to_observers(
@ -118,6 +125,7 @@ async def _start(session_id: UUID, event, who=None) -> None:
level=scenario.level.value,
mode=event.mode,
exercise=event.exercise,
handoff_to_dds=event.handoff_to_dds,
scenario=scenario.model_copy(deep=True),
required_fields=scenario.required_fields,
trainee_name=event.trainee,
@ -188,12 +196,12 @@ async def _stop(session_id: UUID) -> None:
if state.exercise in {Exercise.DDS, Exercise.CARD}:
from app.session.finish import finish
if state.exercise is Exercise.DDS:
if state.exercise is Exercise.DDS or state.handoff_to_dds and state.dispatched_card is not None:
hub.to_station(session_id, SessionEnded(reason=CallEndReason.INSTRUCTOR))
else:
hub.to_trainee(session_id, CallEnded(reason=CallEndReason.INSTRUCTOR))
await finish(session_id, state)
if state.exercise is Exercise.DDS:
if state.exercise is Exercise.DDS or state.handoff_to_dds and state.dispatched_card is not None:
hub.to_station(session_id, ScoreReady(session_id=session_id))
if hub.journal:
await hub.journal.session_ended(session_id, state.ended_at, CallEndReason.INSTRUCTOR.value)

View file

@ -86,7 +86,7 @@ async def _handle(session_id: UUID, state, event) -> None:
_error(session_id, "Служба отсутствует в списке оповещения карточки")
return
phase = REPORT_FOR_STATUS.get(event.status)
if state.exercise is Exercise.DDS and phase is not None and not any(
if (state.exercise is Exercise.DDS or state.handoff_to_dds) and phase is not None and not any(
report.service == event.service and report.phase == phase
for report in state.phone_reports
):
@ -144,7 +144,7 @@ async def _handle(session_id: UUID, state, event) -> None:
state.dds_log.append(("phone.dial", now_utc(), crew))
hub.to_station(session_id, PhoneReport(**report.model_dump()))
case "card.reply":
if state.exercise is not Exercise.DDS or not state.dispatched_card or (
if (state.exercise is not Exercise.DDS and not state.handoff_to_dds) or not state.dispatched_card or (
event.card_id != state.dispatched_card.card_id
):
_error(session_id, "Ответ относится не к текущей карточке")
@ -168,7 +168,7 @@ async def _handle(session_id: UUID, state, event) -> None:
else:
await _finish_dds(session_id, state)
case "station.finish":
if state.exercise is not Exercise.DDS:
if state.exercise is not Exercise.DDS and not state.handoff_to_dds:
_error(session_id, "Операторское занятие завершается после звонка 112")
return
await _finish_dds(session_id, state)