From 68361c66e05136aec6f61bc094850a9ec58406f6 Mon Sep 17 00:00:00 2001 From: kaifarikman Date: Sun, 27 Sep 2026 23:38:20 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D1=83=D1=87=D0=B8=D1=82=D1=8B=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D1=80=D1=83=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=9A?= =?UTF-8?q?=D0=98=D0=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/domain/kio.py | 14 +++++++-- backend/tests/test_ekp.py | 37 ++++++++++++++++++++++++ frontend/src/features/kio-card/fields.ts | 1 + frontend/src/shared/types/generated.ts | 1 + 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/backend/app/domain/kio.py b/backend/app/domain/kio.py index a8158a8..6760c27 100644 --- a/backend/app/domain/kio.py +++ b/backend/app/domain/kio.py @@ -112,6 +112,7 @@ class KIO(BaseModel): description: str | None = None victims_count: int | None = None is_emergency: bool = False + offence_signs: bool | None = None life_threat: bool = False evacuation_needed: bool = False @@ -153,7 +154,7 @@ EDITABLE_KIO_FIELDS: frozenset[str] = frozenset({ "caller_name", "caller_contact", "phone_on_scene", "language", "okato", "address", "street", "building", "entrance", "floor", "intercom_code", "description", "incident_group", "signs", - "incident_type", "victims_count", "is_emergency", "life_threat", + "incident_type", "victims_count", "is_emergency", "offence_signs", "life_threat", "evacuation_needed", "dds", "notify_extra", "fire.fire_nature", "fire.object_kind", "fire.floors", "fire.gasified", "fire.people_inside", "fire.smoke_spread", @@ -193,7 +194,10 @@ def missing_fields(card: KIO, required: list[str]) -> list[str]: #: те же «признаки», только не про тип происшествия, а про обстоятельства: #: пострадавшие поднимают скорую, газификация — МОСГАЗ (domain/ekp.py). MODIFIER_FIELDS: frozenset[str] = frozenset( - {"incident_group", "signs", "victims_count", "life_threat", "evacuation_needed", "fire.gasified"} + { + "incident_group", "signs", "victims_count", "life_threat", + "evacuation_needed", "fire.gasified", "offence_signs", "police.offence_kind", + } ) @@ -208,6 +212,12 @@ def modifiers(card: KIO) -> list[str]: keys.append("evacuation") if card.fire is not None and card.fire.gasified: keys.append("gas") + if card.offence_signs is True or ( + card.police is not None + and card.police.offence_kind is not None + and bool(card.police.offence_kind.strip()) + ): + keys.append("offence") return keys diff --git a/backend/tests/test_ekp.py b/backend/tests/test_ekp.py index 1abcd3d..5ad08ad 100644 --- a/backend/tests/test_ekp.py +++ b/backend/tests/test_ekp.py @@ -409,6 +409,43 @@ def test_gasified_object_brings_mosgaz(): assert "МОСГАЗ" in card.notify +def test_explicit_offence_signs_add_police_and_removal_recalculates_notify(): + from app.domain.kio import apply_patch, modifiers + + signs = ["на улице", "мусор", "открытое пламя"] + base = derive_incident(KIO(signs=signs)) + assert base.incident_code == "1010101" + assert "МВД" not in base.notify + + stated = apply_patch(base, {"offence_signs": True}) + assert "offence" in modifiers(stated) + assert set(stated.notify) == set(base.notify) | {"МВД"} + + denied = apply_patch(stated, {"offence_signs": False}) + assert denied.notify == base.notify + unknown = apply_patch(stated, {"offence_signs": None}) + assert unknown.notify == base.notify + + +def test_offence_kind_also_adds_police_until_both_sources_are_removed(): + from app.domain.kio import apply_patch, modifiers + + base = derive_incident(KIO(signs=["на улице", "мусор", "открытое пламя"])) + stated = apply_patch(base, {"police.offence_kind": "поджог"}) + assert "offence" in modifiers(stated) + assert set(stated.notify) == set(base.notify) | {"МВД"} + + denied_flag = apply_patch(stated, {"offence_signs": False}) + assert "МВД" in denied_flag.notify, "явно указанный вид правонарушения остаётся источником" + with_both = apply_patch(stated, {"offence_signs": True}) + assert modifiers(with_both).count("offence") == 1 + without_kind = apply_patch(with_both, {"police.offence_kind": " "}) + assert "МВД" in without_kind.notify + without_flag = apply_patch(without_kind, {"offence_signs": False}) + assert without_flag.notify == base.notify + assert "offence" not in modifiers(without_flag) + + def test_removed_modifier_recalculates_notify_without_stale_service(): from app.domain.kio import KIO, FireDetails, apply_patch, derive_incident diff --git a/frontend/src/features/kio-card/fields.ts b/frontend/src/features/kio-card/fields.ts index 26f73e3..a5c6848 100644 --- a/frontend/src/features/kio-card/fields.ts +++ b/frontend/src/features/kio-card/fields.ts @@ -97,6 +97,7 @@ export const GROUPS: FieldGroup[] = [ { path: "incident_type", label: "Тип происшествия", kind: "select", options: INCIDENT }, { path: "victims_count", label: "Пострадавших", kind: "number" }, { path: "is_emergency", label: "Признак ЧС", kind: "bool" }, + { path: "offence_signs", label: "Признаки правонарушения", kind: "bool" }, { path: "life_threat", label: "Угроза жизни", kind: "bool" }, { path: "evacuation_needed", label: "Нужна эвакуация", kind: "bool" }, ], diff --git a/frontend/src/shared/types/generated.ts b/frontend/src/shared/types/generated.ts index 3e8d32c..47e00a8 100644 --- a/frontend/src/shared/types/generated.ts +++ b/frontend/src/shared/types/generated.ts @@ -360,6 +360,7 @@ export interface KIO { description?: string | null; victims_count?: number | null; is_emergency?: boolean; + offence_signs?: boolean | null; life_threat?: boolean; evacuation_needed?: boolean; /** Список оповещения */