lct-50: таймеры ДДС — 30 с до открытия карточки, 3 минуты до первой записи

This commit is contained in:
Ivan Gerasimov 2026-09-27 23:38:10 +03:00
commit 51ef99c472
18 changed files with 169 additions and 169 deletions

View file

@ -1,6 +1,6 @@
{
"dds_several_cards": {
"score_auto": 53.3,
"score_auto": 65.3,
"card_status": "processed",
"metrics": [
[
@ -13,7 +13,7 @@
],
[
"dds_ack",
"Карточка 1: Служба 101: первичная отметка за 30 с",
"Карточка 1: Служба 101: открытие карточки за 30 с",
"0.0 с",
true,
1.0,
@ -61,11 +61,11 @@
],
[
"dds_work_time",
"Карточка 1: Отработка карточки ДДС",
"карточка не завершена; 0 с (на 180 с быстрее норматива)",
false,
"Карточка 1: Первая запись по карточке ДДС",
"0 с (на 180 с быстрее норматива)",
true,
1.5,
0.0
1.0
],
[
"dds_primary",
@ -77,7 +77,7 @@
],
[
"dds_ack",
"Карточка 2: СМП: первичная отметка за 30 с",
"Карточка 2: СМП: открытие карточки за 30 с",
"0.0 с",
true,
1.0,
@ -125,11 +125,11 @@
],
[
"dds_work_time",
"Карточка 2: Отработка карточки ДДС",
"карточка не завершена; 0 с (на 180 с быстрее норматива)",
false,
"Карточка 2: Первая запись по карточке ДДС",
"0 с (на 180 с быстрее норматива)",
true,
1.5,
0.0
1.0
],
[
"dds_primary",
@ -141,7 +141,7 @@
],
[
"dds_ack",
"Карточка 3: Служба 101: первичная отметка за 30 с",
"Карточка 3: Служба 101: открытие карточки за 30 с",
"0.0 с",
true,
1.0,
@ -189,11 +189,11 @@
],
[
"dds_work_time",
"Карточка 3: Отработка карточки ДДС",
"карточка не завершена; 0 с (на 180 с быстрее норматива)",
false,
"Карточка 3: Первая запись по карточке ДДС",
"0 с (на 180 с быстрее норматива)",
true,
1.5,
0.0
1.0
]
],
"findings": [
@ -207,11 +207,6 @@
"Карточка 1: Служба 101: ход реагирования не доведён до конца",
"не отмечены: Прибытие, Проведение работ"
],
[
"E3",
"Карточка 1: ДДС: норматив времени отработки карточки не выполнен",
"карточка не завершена; 0 с (на 180 с быстрее норматива)"
],
[
"D2",
"Карточка 2: СМП: «Принята» без назначения бригады и хода реагирования",
@ -222,11 +217,6 @@
"Карточка 2: СМП: ход реагирования не доведён до конца",
"не отмечены: Начало реагирования, Прибытие, Проведение работ"
],
[
"E3",
"Карточка 2: ДДС: норматив времени отработки карточки не выполнен",
"карточка не завершена; 0 с (на 180 с быстрее норматива)"
],
[
"D5",
"Карточка 3: Служба 101: комментарии к статусам не разделяют основание и сведения",
@ -236,17 +226,12 @@
"D6",
"Карточка 3: Служба 101: ход реагирования не доведён до конца",
"не отмечены: Прибытие, Проведение работ"
],
[
"E3",
"Карточка 3: ДДС: норматив времени отработки карточки не выполнен",
"карточка не завершена; 0 с (на 180 с быстрее норматива)"
]
],
"cards": [
[
"fire-apartment-l2",
56.0,
68.0,
"Принято, бригада направлена",
[
"card.ack",
@ -259,7 +244,7 @@
],
[
"t20-2-stroke",
48.0,
60.0,
"",
[
"card.ack",
@ -268,7 +253,7 @@
],
[
"t01-1-fire-container",
56.0,
68.0,
"",
[
"card.ack",
@ -281,7 +266,7 @@
]
},
"dds_early_finish": {
"score_auto": 35.3,
"score_auto": 44.1,
"card_status": "processed",
"metrics": [
[
@ -294,7 +279,7 @@
],
[
"dds_ack",
"Карточка 1: СМП: первичная отметка за 30 с",
"Карточка 1: СМП: открытие карточки за 30 с",
"0.0 с",
true,
1.0,
@ -342,11 +327,11 @@
],
[
"dds_work_time",
"Карточка 1: Отработка карточки ДДС",
"карточка не завершена; 0 с (на 180 с быстрее норматива)",
false,
"Карточка 1: Первая запись по карточке ДДС",
"0 с (на 180 с быстрее норматива)",
true,
1.5,
0.0
1.0
],
[
"dds_primary",
@ -358,16 +343,16 @@
],
[
"dds_ack",
"Карточка 2: Служба 101: первичная отметка за 30 с",
"не отмечено",
"Карточка 2: Служба 101: открытие карточки за 30 с",
"карточка не открыта",
false,
1.0,
null
],
[
"dds_work_time",
"Карточка 2: Отработка карточки ДДС",
"карточка не завершена; время обработки не зафиксировано",
"Карточка 2: Первая запись по карточке ДДС",
"первая запись не добавлена; карточка не открыта",
false,
1.5,
0.0
@ -385,25 +370,25 @@
"не отмечены: Начало реагирования, Прибытие, Проведение работ"
],
[
"E3",
"Карточка 1: ДДС: норматив времени отработки карточки не выполнен",
"карточка не завершена; 0 с (на 180 с быстрее норматива)"
"D1",
"Карточка 2: Служба 101: карточка не открыта",
"карточка не открыта до конца занятия"
],
[
"D1",
"Карточка 2: Служба 101: статус реагирования не проставлен",
"карточка осталась без отметки"
"карточка осталась без первичного статуса"
],
[
"E3",
"Карточка 2: ДДС: норматив времени отработки карточки не выполнен",
"карточка не завершена; время обработки не зафиксировано"
"Карточка 2: ДДС: первая запись по карточке позже норматива",
"первая запись не добавлена; карточка не открыта"
]
],
"cards": [
[
"t20-2-stroke",
48.0,
60.0,
"",
[
"card.ack",
@ -419,7 +404,7 @@
]
},
"handoff_112_to_dds": {
"score_auto": 47.8,
"score_auto": 56.5,
"card_status": "processed",
"metrics": [
[
@ -480,7 +465,7 @@
],
[
"dds_ack",
"Карточка 1: Служба 101: первичная отметка за 30 с",
"Карточка 1: Служба 101: открытие карточки за 30 с",
"0.0 с",
true,
1.0,
@ -528,11 +513,11 @@
],
[
"dds_work_time",
"Карточка 1: Отработка карточки ДДС",
"карточка не завершена; 0 с (на 180 с быстрее норматива)",
false,
"Карточка 1: Первая запись по карточке ДДС",
"0 с (на 180 с быстрее норматива)",
true,
1.5,
0.0
1.0
],
[
"dds_primary",
@ -544,7 +529,7 @@
],
[
"dds_ack",
"Карточка 2: Служба 101: первичная отметка за 30 с",
"Карточка 2: Служба 101: открытие карточки за 30 с",
"0.0 с",
true,
1.0,
@ -592,11 +577,11 @@
],
[
"dds_work_time",
"Карточка 2: Отработка карточки ДДС",
"карточка не завершена; 0 с (на 180 с быстрее норматива)",
false,
"Карточка 2: Первая запись по карточке ДДС",
"0 с (на 180 с быстрее норматива)",
true,
1.5,
0.0
1.0
]
],
"findings": [
@ -630,11 +615,6 @@
"Карточка 1: Служба 101: ход реагирования не доведён до конца",
"не отмечены: Прибытие, Проведение работ"
],
[
"E3",
"Карточка 1: ДДС: норматив времени отработки карточки не выполнен",
"карточка не завершена; 0 с (на 180 с быстрее норматива)"
],
[
"D2",
"Карточка 2: Служба 101: «Принята» без назначения бригады и хода реагирования",
@ -644,17 +624,12 @@
"D6",
"Карточка 2: Служба 101: ход реагирования не доведён до конца",
"не отмечены: Начало реагирования, Прибытие, Проведение работ"
],
[
"E3",
"Карточка 2: ДДС: норматив времени отработки карточки не выполнен",
"карточка не завершена; 0 с (на 180 с быстрее норматива)"
]
],
"cards": [
[
"fire-apartment-l2",
56.0,
68.0,
"",
[
"card.ack",
@ -666,7 +641,7 @@
],
[
"t01-1-fire-container",
48.0,
60.0,
"",
[
"card.ack",
@ -745,7 +720,7 @@
],
[
"dds_ack",
"Служба 101: первичная отметка за 30 с",
"Служба 101: открытие карточки за 30 с",
"0.0 с",
true,
1.0,

View file

@ -56,8 +56,9 @@ def fire():
def accept_with_crew(state: SessionState, card) -> None:
service = state.card_services(card)[0]
card.on_event("dds.open") # карточка открыта: 30 с остановлены
card.set_service_status(service, ServiceStatus.ACCEPTED, "Принято в работу")
card.on_event("card.ack")
card.on_event("dds.record")
card.crew_selected = state.crew_options(card)[0]
card.crew_assignments[service] = card.crew_selected
card.dds_log.append(("crew.select", now_utc(), card.crew_selected))

View file

@ -443,11 +443,13 @@ def test_instructor_criteria_change_timer_and_success_threshold(client, monkeypa
reply_metric = next(item for item in score["metrics"] if item["key"] == "dds_reply")
assert reply_metric["passed"]
assert not any(item["key"] == "dds_grammar" for item in score["metrics"])
# 3 минуты — до первой записи, а она сделана сразу: незавершённая
# работа по времени не штрафуется (ответ заказчика, П.5).
assert (
next(item for item in score["metrics"] if item["key"] == "dds_work_time")[
"passed"
]
is False
is True
)
report = client.get(f"/api/sessions/{session_id}/report").json()
assert report["criteria"] == {
@ -500,9 +502,8 @@ def test_dds_statuses_do_not_require_phone_reports(client):
finding["code"] == "D2" and "бригады" in finding["summary"]
for finding in score["findings"]
)
assert any(
finding["code"] == "E3" and "времени отработки" in finding["summary"]
for finding in score["findings"]
assert not any(finding["code"] == "E3" for finding in score["findings"]), (
"первая запись сделана сразу: дальше работы по времени не нормируются"
)
finding_codes = {finding["code"] for finding in score["findings"]}
penalty_codes = {
@ -703,22 +704,26 @@ def test_complete_dds_workflow_scores_100_with_assignment_without_call(client):
control.__exit__(None, None, None)
def test_dds_card_over_three_minutes_has_e3_finding_and_actual_deviation(client):
def test_late_first_record_has_e3_and_long_work_after_it_does_not(client):
"""П.5: 3 минуты — на открытие и первую запись; дальше работы идут часы."""
session_id, control = start(
client, criteria={"dds_card_work_time_limit_seconds": 60}
)
try:
state = hub.get(session_id)
with client.websocket_connect(f"/ws/station/{session_id}") as station:
read_until(station, "card.received")
received = read_until(station, "card.received")
snapshot = read_until(station, "station.state")["snapshot"]
service = snapshot["services"][0]
station.send_json({"type": "card.open", "card_id": received["card"]["card_id"]})
read_until(station, "station.state")
assert state.desk.active.timers.measured_ms(TimerCode.DDS_ACK) is not None
state.desk.active.timers.timers[TimerCode.DDS_WORK].started_at = time.monotonic() - 61
station.send_json(
{"type": "card.status", "service": service, "status": "accepted",
"comment": "Основание: доклад старшего.\nСведения: карточка принята."}
)
snapshot = read_until(station, "station.state")["snapshot"]
state.desk.active.timers.timers[TimerCode.DDS_WORK].started_at = time.monotonic() - 61
crew = next(
option
for option in snapshot["crew_options"]
@ -740,8 +745,8 @@ def test_dds_card_over_three_minutes_has_e3_finding_and_actual_deviation(client)
)
assert not metric["passed"]
assert "61 с" in metric["fact"] and "+1 с" in metric["fact"]
finding = next(item for item in score["findings"] if item["code"] == "E3")
assert "времени отработки карточки" in finding["summary"]
findings = [item for item in score["findings"] if item["code"] == "E3"]
assert len(findings) == 1 and "первая запись" in findings[0]["summary"]
finally:
control.__exit__(None, None, None)
@ -916,7 +921,9 @@ def test_concurrent_dds_cards_keep_independent_state_and_timers(client):
item["key"] == "dds_grammar"
for item in state.score["metrics"]
)
assert any(item["code"] == "E3" for item in state.score["findings"])
assert not any(item["code"] == "E3" for item in state.score["findings"]), (
"обе карточки получили первую запись сразу"
)
report = client.get(f"/api/sessions/{session_id}/report").json()
assert report["scenario_id"] == "fire-apartment-l2"
assert len(report["card_results"]) == 2

View file

@ -143,8 +143,9 @@ def test_concurrent_dds_queue_round_trips_with_each_timer_and_status():
first_card = state.desk.active
first_id = first_card.card_id
first_service = state.card_services(first_card)[0]
first_card.on_event("dds.open") # карточка открыта: 30 с остановлены
first_card.set_service_status(first_service, ServiceStatus.ACCEPTED, "Принято в работу")
first_card.on_event("card.ack")
first_card.on_event("dds.record")
second_id = state.desk.ordered()[1].card_id
assert state.desk.open(second_id)

View file

@ -62,19 +62,21 @@ def test_card_fill_time_metric_texts(elapsed_ms, completed, fact, passed):
(605_000, True, "605 с (+425 с сверх норматива)", False),
(25_000, True, "25 с (на 155 с быстрее норматива)", True),
(180_000, True, "180 с (точно в норматив)", True),
(None, False, "карточка не завершена; время обработки не зафиксировано", False),
(25_000, False, "карточка не завершена; 25 с (на 155 с быстрее норматива)", False),
(None, False, "первая запись не добавлена; карточка не открыта", False),
(25_000, False, "первая запись не добавлена; 25 с (на 155 с быстрее норматива)", False),
],
)
def test_dds_work_time_metric_texts(elapsed_ms, completed, fact, passed):
metric, finding = time_metric(DDS_WORK_TIME, elapsed_ms, 180_000, completed=completed)
assert metric.key == "dds_work_time"
assert metric.title == "Отработка карточки ДДС"
assert metric.title == "Первая запись по карточке ДДС"
assert metric.fact == fact
assert metric.norm == "завершить за 180 с"
assert metric.norm == "открыть карточку и добавить первую запись (статус и текст) за 180 с"
assert metric.passed is passed
assert (finding is None) is passed
if finding is not None:
assert finding.summary == "ДДС: норматив времени отработки карточки не выполнен"
assert (finding.fact, finding.norm) == (fact, "завершить за 180 с")
assert finding.summary == "ДДС: первая запись по карточке позже норматива"
assert (finding.fact, finding.norm) == (
fact, "открыть карточку и добавить первую запись (статус и текст) за 180 с"
)