feat: учитывать правонарушение в КИО

This commit is contained in:
kaifarikman 2026-09-27 23:38:20 +03:00
commit 68361c66e0
4 changed files with 51 additions and 2 deletions

View file

@ -112,6 +112,7 @@ class KIO(BaseModel):
description: str | None = None description: str | None = None
victims_count: int | None = None victims_count: int | None = None
is_emergency: bool = False is_emergency: bool = False
offence_signs: bool | None = None
life_threat: bool = False life_threat: bool = False
evacuation_needed: bool = False evacuation_needed: bool = False
@ -153,7 +154,7 @@ EDITABLE_KIO_FIELDS: frozenset[str] = frozenset({
"caller_name", "caller_contact", "phone_on_scene", "language", "caller_name", "caller_contact", "phone_on_scene", "language",
"okato", "address", "street", "building", "entrance", "floor", "okato", "address", "street", "building", "entrance", "floor",
"intercom_code", "description", "incident_group", "signs", "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", "evacuation_needed", "dds", "notify_extra",
"fire.fire_nature", "fire.object_kind", "fire.floors", "fire.gasified", "fire.fire_nature", "fire.object_kind", "fire.floors", "fire.gasified",
"fire.people_inside", "fire.smoke_spread", "fire.people_inside", "fire.smoke_spread",
@ -193,7 +194,10 @@ def missing_fields(card: KIO, required: list[str]) -> list[str]:
#: те же «признаки», только не про тип происшествия, а про обстоятельства: #: те же «признаки», только не про тип происшествия, а про обстоятельства:
#: пострадавшие поднимают скорую, газификация — МОСГАЗ (domain/ekp.py). #: пострадавшие поднимают скорую, газификация — МОСГАЗ (domain/ekp.py).
MODIFIER_FIELDS: frozenset[str] = frozenset( 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") keys.append("evacuation")
if card.fire is not None and card.fire.gasified: if card.fire is not None and card.fire.gasified:
keys.append("gas") 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 return keys

View file

@ -409,6 +409,43 @@ def test_gasified_object_brings_mosgaz():
assert "МОСГАЗ" in card.notify 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(): def test_removed_modifier_recalculates_notify_without_stale_service():
from app.domain.kio import KIO, FireDetails, apply_patch, derive_incident from app.domain.kio import KIO, FireDetails, apply_patch, derive_incident

View file

@ -97,6 +97,7 @@ export const GROUPS: FieldGroup[] = [
{ path: "incident_type", label: "Тип происшествия", kind: "select", options: INCIDENT }, { path: "incident_type", label: "Тип происшествия", kind: "select", options: INCIDENT },
{ path: "victims_count", label: "Пострадавших", kind: "number" }, { path: "victims_count", label: "Пострадавших", kind: "number" },
{ path: "is_emergency", label: "Признак ЧС", kind: "bool" }, { path: "is_emergency", label: "Признак ЧС", kind: "bool" },
{ path: "offence_signs", label: "Признаки правонарушения", kind: "bool" },
{ path: "life_threat", label: "Угроза жизни", kind: "bool" }, { path: "life_threat", label: "Угроза жизни", kind: "bool" },
{ path: "evacuation_needed", label: "Нужна эвакуация", kind: "bool" }, { path: "evacuation_needed", label: "Нужна эвакуация", kind: "bool" },
], ],

View file

@ -360,6 +360,7 @@ export interface KIO {
description?: string | null; description?: string | null;
victims_count?: number | null; victims_count?: number | null;
is_emergency?: boolean; is_emergency?: boolean;
offence_signs?: boolean | null;
life_threat?: boolean; life_threat?: boolean;
evacuation_needed?: boolean; evacuation_needed?: boolean;
/** Список оповещения */ /** Список оповещения */