From eb5de96ed7f994196011dfff410dc4bab029385a Mon Sep 17 00:00:00 2001 From: gglamer Date: Sat, 26 Sep 2026 19:36:51 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=81=D0=BD=D0=B8=D0=BC=D0=BE=D0=BA=20?= =?UTF-8?q?=D1=81=D0=B5=D1=81=D1=81=D0=B8=D0=B8=20=D1=81=D0=BE=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D1=8F=D0=B5=D1=82=20=D1=80=D0=B0=D1=81=D0=BA=D1=80?= =?UTF-8?q?=D1=8B=D1=82=D1=8B=D0=B5=20=D1=84=D0=B0=D0=BA=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=88=D1=91=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BA=D0=B0=D1=80?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=B5=D0=BA=20=D0=94=D0=94=D0=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/session/checkpoint.py | 18 +++++++ backend/tests/test_session_checkpoint.py | 68 +++++++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/backend/app/session/checkpoint.py b/backend/app/session/checkpoint.py index efb84be..0b27515 100644 --- a/backend/app/session/checkpoint.py +++ b/backend/app/session/checkpoint.py @@ -151,11 +151,20 @@ def dump_state(state: SessionState) -> dict: "findings": [finding.model_dump(mode="json") for finding in item.findings], "actions": item.actions, "duration_ms": item.duration_ms, + "title": item.title, + "address": item.address, + "description": item.description, + "incident_type": item.incident_type, + "victims_count": item.victims_count, + "received_at": item.received_at.isoformat() if item.received_at else None, + "managed_service": item.managed_service, + "recipient_services": item.recipient_services, } for item in state.dds_completed ], "reply_text": state.reply_text, "reply_log": [[at.isoformat(), text] for at, text in state.reply_log], + "text_revealed_facts": state.text_revealed_facts, "resolved_outcome": state.resolved_outcome, "resolve_comment": state.resolve_comment, "processed_station_commands": state.processed_station_commands[-512:], @@ -312,12 +321,21 @@ def load_state(payload: dict, saved_at: datetime) -> SessionState: for finding in item.get("findings", [])], actions=list(item.get("actions") or []), duration_ms=int(item.get("duration_ms", 0)), + title=item.get("title"), + address=item.get("address"), + description=item.get("description"), + incident_type=item.get("incident_type"), + victims_count=item.get("victims_count"), + received_at=_dt(item.get("received_at")), + managed_service=item.get("managed_service"), + recipient_services=list(item.get("recipient_services") or []), ) for item in payload.get("dds_completed", []) ], reply_text=payload.get("reply_text", ""), reply_log=[(datetime.fromisoformat(at), text) for at, text in payload.get("reply_log", [])], + text_revealed_facts=dict(payload.get("text_revealed_facts") or {}), resolved_outcome=payload.get("resolved_outcome"), resolve_comment=payload.get("resolve_comment", ""), processed_station_commands=list(payload.get("processed_station_commands") or [])[-512:], diff --git a/backend/tests/test_session_checkpoint.py b/backend/tests/test_session_checkpoint.py index 464522f..9871457 100644 --- a/backend/tests/test_session_checkpoint.py +++ b/backend/tests/test_session_checkpoint.py @@ -6,15 +6,23 @@ from pathlib import Path from uuid import uuid4 import pytest -from app.domain.events import CommandAck, CallStarted, Exercise, LessonCriteria, SessionMode +from app.domain.events import ( + CallStarted, + CommandAck, + Exercise, + LessonCriteria, + Metric, + SessionMode, +) from app.domain.kio import KIO from app.domain.statuses import PhoneCallPending, ServiceStatus +from app.domain.taxonomy import ErrorCode, Finding, FindingSource from app.domain.timers import TimerCode from app.scenarios.loader import load_file from app.session.checkpoint import dump_state, load_state from app.session.dds import deliver_due_cards, prepare_handoff_queue, prepare_queue from app.session.hub import LEASE_FENCED_MESSAGE, SessionHub -from app.session.state import SessionState, now_utc +from app.session.state import DdsCardRecord, SessionState, now_utc LIBRARY = Path(__file__).resolve().parents[2] / "scenarios" @@ -80,6 +88,62 @@ def test_active_dds_session_round_trips_without_losing_work(): assert timer.limit_ms == 45_000 +def completed_card() -> DdsCardRecord: + return DdsCardRecord( + card_id=uuid4(), + scenario_id="fire-apartment-l2", + reply_text="Бригада направлена.", + metrics=[Metric(key="dds_ack", title="Приём", fact="20 с", norm="30 с", passed=True)], + findings=[Finding(code=ErrorCode.D1, source=FindingSource.DISPATCHER, + summary="Поздний статус", fact="статус через 5 мин")], + actions=[{"action": "crew.select", "detail": "АЦ-1"}], + duration_ms=61_000, + title="Пожар в квартире", + address="улица Ленина, 14, кв. 5", + description="дым из окна", + incident_type="пожар", + victims_count=2, + received_at=datetime(2026, 9, 26, 10, 15, 30, 123000, tzinfo=UTC), + managed_service="01", + recipient_services=["01", "03"], + ) + + +def test_text_revealed_facts_and_completed_cards_round_trip_whole(): + before = dds_state() + before.text_revealed_facts = {"address": "улица Ленина, 14", "floor": "5 этаж, уточнено"} + before.dds_completed = [completed_card()] + + restored = load_state(dump_state(before), now_utc()) + + assert restored.text_revealed_facts == before.text_revealed_facts + assert restored.dds_completed == before.dds_completed + assert restored.dds_completed[0].received_at.tzinfo is not None + + +def test_legacy_checkpoint_without_new_keys_gets_defaults(): + before = dds_state() + before.text_revealed_facts = {"address": "улица Ленина, 14"} + before.dds_completed = [completed_card()] + payload = dump_state(before) + del payload["text_revealed_facts"] + legacy_keys = {"card_id", "scenario_id", "reply_text", "metrics", + "findings", "actions", "duration_ms"} + payload["dds_completed"] = [ + {key: value for key, value in item.items() if key in legacy_keys} + for item in payload["dds_completed"] + ] + + restored = load_state(payload, now_utc()) + + assert restored.text_revealed_facts == {} + card = restored.dds_completed[0] + assert card.card_id == before.dds_completed[0].card_id + assert (card.title, card.address, card.description, card.incident_type, + card.victims_count, card.received_at, card.managed_service) == (None,) * 7 + assert card.recipient_services == [] + + def test_checkpoint_rejects_unknown_format_version(): payload = dump_state(dds_state()) payload["version"] = 999