lct-55: ошибка адреса и сообщение в 112
This commit is contained in:
parent
29aca65e8c
commit
f0c9015f2b
14 changed files with 267 additions and 7 deletions
|
|
@ -678,6 +678,22 @@ class CardReply(BaseModel):
|
|||
text: str = Field(max_length=2000)
|
||||
|
||||
|
||||
class CardErrorReport112(BaseModel):
|
||||
"""Сообщение диспетчера в 112 после доклада бригады с места."""
|
||||
|
||||
type: Literal["card.error_report_112"] = "card.error_report_112"
|
||||
card_id: UUID
|
||||
text: str = Field(min_length=10, max_length=500)
|
||||
|
||||
@field_validator("text")
|
||||
@classmethod
|
||||
def meaningful_text(cls, value: str) -> str:
|
||||
value = value.strip()
|
||||
if len(value) < 10:
|
||||
raise ValueError("Укажите, что сообщено оператору 112")
|
||||
return value
|
||||
|
||||
|
||||
class CardNext(BaseModel):
|
||||
type: Literal["card.next"] = "card.next"
|
||||
card_id: UUID
|
||||
|
|
@ -721,7 +737,7 @@ ServerToStation = Annotated[
|
|||
StationToServer = Annotated[
|
||||
CardAck | CardBounce | ServiceStatusSet | CrewSelect | PhoneDial | PhoneBrief | PhoneCheck
|
||||
| PhoneHangup | PhoneReply | AdjacentDial | StationFinish
|
||||
| CardReply | CardNext | CardOpen
|
||||
| CardReply | CardErrorReport112 | CardNext | CardOpen
|
||||
| ZoneDecision | CrewDispatched | CrewArrived,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -288,6 +288,8 @@ class StationSnapshot(BaseModel):
|
|||
phone_reports: list[PhoneReportRecord] = []
|
||||
phone_lines: list[PhoneLineRecord] = []
|
||||
phone_pending: PhoneCallPending | None = None
|
||||
card_error_discovered: bool = False
|
||||
report_112_text: str | None = None
|
||||
card_id: UUID | None = None
|
||||
card_index: int = 1
|
||||
card_total: int = 1
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@
|
|||
на старте приложения, а не тихо игнорироваться и всплывать посреди занятия.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
|
||||
from app.domain.classifiers import DDSCode, IncidentType, Level, Outcome
|
||||
from app.domain.events import Mood
|
||||
from app.domain.kio import EDITABLE_KIO_FIELDS, KIO
|
||||
from app.domain.statuses import NEXT, SERVICE_STATUS_LABELS, ServiceStatus
|
||||
from app.scoring.address import address_matches
|
||||
from app.scoring.taxonomy import METRIC_MAP
|
||||
|
||||
|
||||
|
|
@ -174,6 +177,21 @@ FALLBACK_NOTIFY: dict[str, str] = {
|
|||
}
|
||||
|
||||
|
||||
class DdsCardError(Strict):
|
||||
"""Заложенное расхождение готовой карточки с эталоном для занятия ДДС."""
|
||||
|
||||
field: Literal["address"]
|
||||
card_value: str = Field(min_length=3)
|
||||
|
||||
@field_validator("card_value")
|
||||
@classmethod
|
||||
def nonempty_card_value(cls, value: str) -> str:
|
||||
value = value.strip()
|
||||
if len(value) < 3:
|
||||
raise ValueError("dds_card_error.card_value: укажите неверный адрес карточки")
|
||||
return value
|
||||
|
||||
|
||||
class Scenario(Strict):
|
||||
id: str
|
||||
title: str
|
||||
|
|
@ -202,6 +220,7 @@ class Scenario(Strict):
|
|||
# справка и передача в другой регион разбираются в lct-36.
|
||||
outcome: Outcome = Outcome.CARD
|
||||
dds_decision: DdsDecision = DdsDecision()
|
||||
dds_card_error: DdsCardError | None = None
|
||||
|
||||
# Готовая КИО, сформированная курсантом и утверждённая преподавателем.
|
||||
# Для системных сценариев поле отсутствует; ДДС использует снимок как
|
||||
|
|
@ -258,6 +277,19 @@ class Scenario(Strict):
|
|||
)
|
||||
return self
|
||||
|
||||
@model_validator(mode="after")
|
||||
def valid_dds_card_error(self):
|
||||
error = self.dds_card_error
|
||||
if error is None:
|
||||
return self
|
||||
if not self.ground_truth.address or not self.ground_truth.address.strip():
|
||||
raise ValueError("dds_card_error: для ошибки адреса нужен ground_truth.address")
|
||||
if self.student_card is not None:
|
||||
raise ValueError("dds_card_error: используйте отдельный учебный сценарий без student_card")
|
||||
if address_matches(self.ground_truth.address, error.card_value):
|
||||
raise ValueError("dds_card_error: адрес карточки должен отличаться от эталона")
|
||||
return self
|
||||
|
||||
@model_validator(mode="after")
|
||||
def unique_reference_ids(self):
|
||||
for label, values in (
|
||||
|
|
|
|||
|
|
@ -99,6 +99,11 @@ def to_csv(report: SessionReport) -> bytes:
|
|||
row("Сверка самооценки", "", label, ", ".join(values))
|
||||
for number, item in enumerate(report.notes, 1):
|
||||
row("Пометки преподавателя", number, item.transcript_ref, item.text, item.author)
|
||||
for card_number, card in enumerate(report.card_results, 1):
|
||||
for action in card.actions:
|
||||
if action.get("type") == "card.error_report_112":
|
||||
row("Сообщение в 112", card_number, "Ошибка карточки",
|
||||
action.get("text", ""), action.get("at", ""))
|
||||
return ("\N{ZERO WIDTH NO-BREAK SPACE}" + output.getvalue()).encode("utf-8")
|
||||
|
||||
|
||||
|
|
@ -259,6 +264,17 @@ def to_pdf(report: SessionReport) -> bytes:
|
|||
for item in report.notes:
|
||||
story.append(p(f"{item.transcript_ref} - {item.author}: {item.text}"))
|
||||
|
||||
messages_112 = [
|
||||
(number, action)
|
||||
for number, card in enumerate(report.card_results, 1)
|
||||
for action in card.actions
|
||||
if action.get("type") == "card.error_report_112"
|
||||
]
|
||||
if messages_112:
|
||||
section("Сообщения в 112 об ошибках карточек")
|
||||
for number, action in messages_112:
|
||||
story.append(p(f"Карточка {number}, {action.get('at', '')}: {action.get('text', '')}"))
|
||||
|
||||
output = io.BytesIO()
|
||||
document = SimpleDocTemplate(
|
||||
output, pagesize=A4, leftMargin=42, rightMargin=42,
|
||||
|
|
|
|||
|
|
@ -195,6 +195,9 @@ def build_card(
|
|||
)
|
||||
if service:
|
||||
kio = apply_patch(kio, {"dds": service})
|
||||
if scenario.dds_card_error is not None:
|
||||
# Ошибка относится к выданному снимку; эталон сценария остаётся верным.
|
||||
kio = kio.model_copy(update={"address": scenario.dds_card_error.card_value})
|
||||
kio.response_status = ResponseStatus.TRANSFERRED
|
||||
timers = SessionTimers(limits=dict(limits))
|
||||
timers.on_event("dds.dispatch")
|
||||
|
|
@ -401,7 +404,14 @@ def _deliver_card_adjacent(state: "SessionState", card: DdsLiveCard, now: dateti
|
|||
def _finish_phone_call(card: DdsLiveCard) -> list[Any]:
|
||||
call = card.phone_pending
|
||||
assert call is not None
|
||||
text = _report_text(call.phase, call.crew, card.dispatched_card.address or "из карточки")
|
||||
error = card.scenario.dds_card_error
|
||||
if call.phase == "arrived" and error is not None and error.field == "address":
|
||||
actual = card.scenario.ground_truth.address
|
||||
text = (f"{call.crew}: прибыли по адресу {actual}. "
|
||||
f"В карточке указан {card.dispatched_card.address}; "
|
||||
"адрес не совпадает. Сообщите об ошибке в 112.")
|
||||
else:
|
||||
text = _report_text(call.phase, call.crew, card.dispatched_card.address or "из карточки")
|
||||
line = _line(card, "crew", text)
|
||||
report = PhoneReportRecord(service=call.service, crew=call.crew,
|
||||
phase=call.phase, text=text, at=now_utc())
|
||||
|
|
@ -749,6 +759,16 @@ class DdsDesk(BaseModel):
|
|||
return DeskOutcome()
|
||||
card.reply_text = command.text
|
||||
card.reply_log.append((now_utc(), command.text))
|
||||
case "card.error_report_112":
|
||||
if command.card_id != card.card_id:
|
||||
return _refused("Сообщение относится не к текущей карточке")
|
||||
if card.scenario.dds_card_error is None or not any(
|
||||
report.phase == "arrived" for report in card.phone_reports
|
||||
):
|
||||
return _refused("Сначала получите доклад бригады с места об ошибке карточки")
|
||||
if any(action == "card.error_report_112" for action, _at, _detail in card.dds_log):
|
||||
return DeskOutcome()
|
||||
card.dds_log.append(("card.error_report_112", now_utc(), command.text.strip()))
|
||||
case "card.open":
|
||||
if not dds_lesson or not self.open(command.card_id):
|
||||
return _refused("Карточка отсутствует в текущей очереди")
|
||||
|
|
|
|||
|
|
@ -436,6 +436,13 @@ class SessionState(PersistedSession):
|
|||
phone_reports=list(card.phone_reports) if card is not None else [],
|
||||
phone_lines=list(card.phone_lines) if card is not None else [],
|
||||
phone_pending=card.phone_pending if card is not None else None,
|
||||
card_error_discovered=(card.scenario.dds_card_error is not None and any(
|
||||
report.phase == "arrived" for report in card.phone_reports
|
||||
)) if card is not None else False,
|
||||
report_112_text=(next(
|
||||
(detail for action, _at, detail in reversed(card.dds_log)
|
||||
if action == "card.error_report_112"), None,
|
||||
) if card is not None else None),
|
||||
card_id=card.card_id if card is not None else None,
|
||||
card_index=(desk.card_index + 1 if card is not None
|
||||
else min(desk.next_index + 1, len(desk.scenarios))),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import asyncio
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
|
@ -17,6 +17,7 @@ from app.domain.timers import TimerCode
|
|||
from app.main import app
|
||||
from app.scenarios import store
|
||||
from app.session.hub import hub
|
||||
from app.session.checkpoint import dump_state, load_state
|
||||
from app.session.store import MemorySessionStore
|
||||
|
||||
|
||||
|
|
@ -836,6 +837,76 @@ def send_status(station, service, status):
|
|||
read_until(station, "station.state")
|
||||
|
||||
|
||||
def test_crew_discovers_card_address_error_and_dispatcher_reports_it_to_112(client):
|
||||
session_id, control = start(client, scenario_id="dds-card-address-error")
|
||||
try:
|
||||
state = hub.get(session_id)
|
||||
card = state.desk.active
|
||||
assert card.dispatched_card.address == "улица Ленина, 16"
|
||||
assert card.scenario.ground_truth.address == "улица Ленина, 14"
|
||||
with client.websocket_connect(f"/ws/station/{session_id}") as station:
|
||||
service = accept_with_crew(station)
|
||||
station.send_json({
|
||||
"type": "card.error_report_112", "card_id": str(card.card_id),
|
||||
"text": "В карточке неверный адрес",
|
||||
})
|
||||
assert "Сначала получите доклад" in read_until(station, "error")["message"]
|
||||
complete_phone_call(station, state, "dispatched")
|
||||
read_until(station, "station.state")
|
||||
send_status(station, service, "responding")
|
||||
report = complete_phone_call(station, state, "arrived")
|
||||
snapshot = read_until(station, "station.state")["snapshot"]
|
||||
assert "улица Ленина, 14" in report["text"]
|
||||
assert "улица Ленина, 16" in report["text"]
|
||||
assert snapshot["card_error_discovered"] is True
|
||||
assert snapshot["report_112_text"] is None
|
||||
|
||||
message = "В карточке дом 16; бригада на месте установила дом 14."
|
||||
station.send_json({
|
||||
"type": "card.error_report_112", "card_id": str(card.card_id), "text": message,
|
||||
})
|
||||
snapshot = read_until(station, "station.state")["snapshot"]
|
||||
assert snapshot["report_112_text"] == message
|
||||
station.send_json({
|
||||
"type": "card.error_report_112", "card_id": str(card.card_id), "text": message,
|
||||
})
|
||||
assert sum(action == "card.error_report_112" for action, _, _ in card.dds_log) == 1
|
||||
|
||||
active = client.get("/api/sessions/active").json()
|
||||
teacher = next(row for row in active if row["session_id"] == str(session_id))
|
||||
assert teacher["dds_snapshot"]["report_112_text"] == message
|
||||
restored = load_state(dump_state(state), datetime.now(UTC))
|
||||
assert restored.station_snapshot().report_112_text == message
|
||||
station.send_json({"type": "station.finish"})
|
||||
read_until(station, "score.ready")
|
||||
|
||||
report = client.get(f"/api/sessions/{session_id}/report").json()
|
||||
actions = report["card_results"][0]["actions"]
|
||||
assert any(action["type"] == "card.error_report_112" and action["text"] == message
|
||||
for action in actions)
|
||||
assert message in client.get(f"/api/sessions/{session_id}/report.csv").text
|
||||
finally:
|
||||
control.__exit__(None, None, None)
|
||||
|
||||
|
||||
def test_report_to_112_is_unavailable_without_planted_card_error(client):
|
||||
session_id, control = start(client)
|
||||
try:
|
||||
state = hub.get(session_id)
|
||||
with client.websocket_connect(f"/ws/station/{session_id}") as station:
|
||||
read_until(station, "card.received")
|
||||
snapshot = read_until(station, "station.state")["snapshot"]
|
||||
assert snapshot["card_error_discovered"] is False
|
||||
station.send_json({
|
||||
"type": "card.error_report_112", "card_id": str(state.desk.active.card_id),
|
||||
"text": "Сообщаю об ошибке карточки в 112",
|
||||
})
|
||||
assert "Сначала получите доклад" in read_until(station, "error")["message"]
|
||||
assert not state.desk.active.dds_log
|
||||
finally:
|
||||
control.__exit__(None, None, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("reaction", "passed"), [
|
||||
("fast", True), ("slow", False), ("missing", False), ("early", True),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from fastapi.testclient import TestClient
|
|||
from app.api.auth import Principal
|
||||
from app.api.http import sessions
|
||||
from app.config import get_settings
|
||||
from app.domain.events import SessionReport
|
||||
from app.domain.events import DdsCardReport, SessionReport
|
||||
from app.domain.roles import Role
|
||||
from app.main import app
|
||||
from app.scoring.export import _cell, certificate_pdf, to_csv, to_pdf
|
||||
|
|
@ -83,6 +83,31 @@ def test_csv_shows_enabled_report_reaction_limit():
|
|||
assert ["Критерии", "", "Лимит реакции на доклад бригады, с", "20", ""] in rows
|
||||
|
||||
|
||||
def test_card_error_message_to_112_is_in_csv_and_pdf(tmp_path):
|
||||
report = sample_report()
|
||||
message = "В карточке дом 16; по докладу бригады фактически дом 14."
|
||||
report.card_results = [DdsCardReport(
|
||||
card_id=uuid4(), scenario_id="dds-card-address-error", score_auto=100,
|
||||
reply_text="", metrics=[], findings=[], actions=[{
|
||||
"type": "card.error_report_112", "text": message,
|
||||
"at": "2026-09-21T12:05:00+00:00",
|
||||
}],
|
||||
)]
|
||||
|
||||
rows = list(csv.reader(io.StringIO(to_csv(report).decode("utf-8-sig"))))
|
||||
assert any(row[0] == "Сообщение в 112" and row[3] == message for row in rows)
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
if shutil.which("pdftotext"):
|
||||
path = tmp_path / "dds-error.pdf"
|
||||
path.write_bytes(to_pdf(report))
|
||||
extracted = subprocess.check_output(["pdftotext", str(path), "-"], text=True)
|
||||
assert "Сообщения в 112 об ошибках карточек" in extracted
|
||||
assert message in extracted
|
||||
|
||||
|
||||
def test_pdf_supports_cyrillic_and_spans_pages(tmp_path):
|
||||
data = to_pdf(sample_report(long=True))
|
||||
assert data.startswith(b"%PDF-")
|
||||
|
|
|
|||
|
|
@ -236,8 +236,8 @@ def test_dispatcher_metric_dds_ack_excludes_pause_from_elapsed(clock):
|
|||
clock.advance(60)
|
||||
state.resume()
|
||||
clock.advance(15)
|
||||
card.set_service_status(service, ServiceStatus.ACCEPTED, "Принято")
|
||||
card.on_event("dds.open")
|
||||
card.set_service_status(service, ServiceStatus.ACCEPTED, "Принято")
|
||||
|
||||
metric = next(m for m in dispatcher_metrics(card, 30_000, services=services) if m.key == "dds_ack")
|
||||
assert metric.passed is True
|
||||
|
|
|
|||
|
|
@ -103,6 +103,8 @@ export function Debrief({ report, big = false }: { report: SessionReport; big?:
|
|||
{card.address ? ` · ${card.address}` : ""}</summary>
|
||||
{card.managed_service && <p><strong>Ответственная служба:</strong> {card.managed_service}</p>}
|
||||
<p><strong>Ответ диспетчера:</strong> {card.reply_text || "не внесён"}</p>
|
||||
{(card.actions ?? []).filter((action) => action.type === "card.error_report_112").map((action, actionIndex) =>
|
||||
<p key={`report-112-${actionIndex}`}><strong>Сообщение об ошибке в 112:</strong> {String(action.text ?? "")}</p>)}
|
||||
{card.metrics.some((metric) => !metric.passed || (metric.credit != null && metric.credit < 1)) && <ul>
|
||||
{card.metrics.filter((metric) => !metric.passed || (metric.credit != null && metric.credit < 1)).map((metric) =>
|
||||
<li key={metric.key} className={metric.passed ? "ref" : "state-violated"}>
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ export function Dds() {
|
|||
const [crewReplyText, setCrewReplyText] = useState("");
|
||||
const [adjacentTarget, setAdjacentTarget] = useState("");
|
||||
const [adjacentText, setAdjacentText] = useState("");
|
||||
const [report112Text, setReport112Text] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [ended, setEnded] = useState(false);
|
||||
const [paused, setPaused] = useState(false);
|
||||
|
|
@ -295,6 +296,7 @@ export function Dds() {
|
|||
setCrewReplyText("");
|
||||
setAdjacentTarget("");
|
||||
setAdjacentText("");
|
||||
setReport112Text("");
|
||||
setZone(null);
|
||||
setScoreReady(false);
|
||||
setView(openCardAfterSwitch.current ? "card" : "registry");
|
||||
|
|
@ -719,6 +721,20 @@ export function Dds() {
|
|||
{adjacentLines.map((line, index) => <p key={`${line.at}-${index}`}><small>{time(line.at)} · {line.speaker === "crew" ? line.crew : "Диспетчер ДДС"}</small><br />{line.text}</p>)}
|
||||
</div>}
|
||||
</>}
|
||||
{station?.card_error_discovered && <div className="dds-phone-form">
|
||||
<h3>Сообщение об ошибке карточки в 112</h3>
|
||||
{station.report_112_text ? <p>Сообщено в 112: {station.report_112_text}</p> : <>
|
||||
<p>После доклада бригады сообщите оператору 112, что указано в карточке и что установлено на месте.</p>
|
||||
<label htmlFor="dds-report-112">Содержание сообщения</label>
|
||||
<textarea id="dds-report-112" rows={3} value={report112Text}
|
||||
disabled={!isConnected || ended || paused}
|
||||
onChange={(event) => setReport112Text(event.target.value)} />
|
||||
<button type="button" disabled={!cardId || !isConnected || ended || paused || report112Text.trim().length < 10}
|
||||
onClick={() => send({ type: "card.error_report_112", card_id: cardId, text: report112Text })}>
|
||||
Зафиксировать сообщение в 112
|
||||
</button>
|
||||
</>}
|
||||
</div>}
|
||||
</section>}
|
||||
{pane === "reply" && <section className="dds-panel dds-reply">
|
||||
<div className="dds-panel-title"><h2>Рабочая заметка диспетчера</h2>
|
||||
|
|
|
|||
|
|
@ -393,6 +393,9 @@ export function Instructor() {
|
|||
<small>{report.text}</small>
|
||||
</p>)}
|
||||
</div> : null}
|
||||
{item.dds_snapshot.report_112_text && <p className="active-session-log">
|
||||
<strong>Сообщено в 112 об ошибке карточки:</strong> {item.dds_snapshot.report_112_text}
|
||||
</p>}
|
||||
</details>}</td>
|
||||
<td>{item.exercise === "dds" ? `Открытых карточек: ${item.dds_open_cards}` : "—"}</td>
|
||||
<td className={item.dds_overdue_cards ? "state-violated" : "state-ok"}>
|
||||
|
|
|
|||
|
|
@ -108,6 +108,13 @@ export interface CardBriefing {
|
|||
handoff_to_dds?: boolean;
|
||||
}
|
||||
|
||||
/** Сообщение диспетчера в 112 после доклада бригады с места. */
|
||||
export interface CardErrorReport112 {
|
||||
type: "card.error_report_112";
|
||||
card_id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface CardNext {
|
||||
type: "card.next";
|
||||
card_id: string;
|
||||
|
|
@ -719,6 +726,8 @@ export interface StationSnapshot {
|
|||
phone_reports?: Array<PhoneReportRecord>;
|
||||
phone_lines?: Array<PhoneLineRecord>;
|
||||
phone_pending?: PhoneCallPending | null;
|
||||
card_error_discovered?: boolean;
|
||||
report_112_text?: string | null;
|
||||
card_id?: string | null;
|
||||
card_index?: number;
|
||||
card_total?: number;
|
||||
|
|
@ -847,7 +856,7 @@ export type InstructorToServer = ScenarioStart | DirectorInject | ReferencePlay
|
|||
|
||||
export type ServerToStation = CardReceived | StationState | PhoneLine | PhoneReport | TimerTick | SessionEnded | ScoreReady | CommandAck | SessionPaused | ErrorEvent;
|
||||
|
||||
export type StationToServer = CardAck | CardBounce | ServiceStatusSet | CrewSelect | PhoneDial | PhoneBrief | PhoneCheck | PhoneHangup | PhoneReply | AdjacentDial | StationFinish | CardReply | CardNext | CardOpen | ZoneDecision | CrewDispatched | CrewArrived;
|
||||
export type StationToServer = CardAck | CardBounce | ServiceStatusSet | CrewSelect | PhoneDial | PhoneBrief | PhoneCheck | PhoneHangup | PhoneReply | AdjacentDial | StationFinish | CardReply | CardErrorReport112 | CardNext | CardOpen | ZoneDecision | CrewDispatched | CrewArrived;
|
||||
|
||||
export type EventName =
|
||||
"adjacent.dial"
|
||||
|
|
@ -864,6 +873,7 @@ export type EventName =
|
|||
| "card.ack"
|
||||
| "card.bounce"
|
||||
| "card.briefing"
|
||||
| "card.error_report_112"
|
||||
| "card.next"
|
||||
| "card.open"
|
||||
| "card.received"
|
||||
|
|
|
|||
40
scenarios/dds-card-address-error.yaml
Normal file
40
scenarios/dds-card-address-error.yaml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Учебная ошибка уже переданной КИО: бригада обнаруживает верный адрес на месте.
|
||||
# Курсанту ДДС нужно сообщить расхождение оператору 112; автооценки этого шага нет.
|
||||
id: dds-card-address-error
|
||||
title: "Ошибка адреса в готовой карточке пожара"
|
||||
type: fire
|
||||
level: L2
|
||||
topics: [fires]
|
||||
modes: [training]
|
||||
signs: [жилой дом, балкон, открытое пламя]
|
||||
|
||||
persona:
|
||||
base: worried
|
||||
arc:
|
||||
- { stage: registration, mood: worried }
|
||||
|
||||
first_line: "На балконе пожар, приезжайте на улицу Ленина, дом 14."
|
||||
|
||||
facts:
|
||||
- id: f_address
|
||||
value: "улица Ленина, 14, квартира 47"
|
||||
reveal_on: { question: q_address }
|
||||
- id: f_what_burns
|
||||
value: "горит балкон, жильцы выходят из квартиры"
|
||||
reveal_on: { question: q_what_burns }
|
||||
|
||||
checklist:
|
||||
- { id: q_address, fact: f_address, question: "Уточните адрес происшествия" }
|
||||
- { id: q_what_burns, fact: f_what_burns, question: "Что именно горит?" }
|
||||
|
||||
required_fields: [address, incident_type, description]
|
||||
ground_truth:
|
||||
address: "улица Ленина, 14"
|
||||
victims: 0
|
||||
|
||||
dds_card_error:
|
||||
field: address
|
||||
card_value: "улица Ленина, 16"
|
||||
|
||||
tree:
|
||||
pregenerated: false
|
||||
Loading…
Reference in a new issue