diff --git a/backend/app/services/exporters/full_report_docx.py b/backend/app/services/exporters/full_report_docx.py index 68171ea2..2860190d 100644 --- a/backend/app/services/exporters/full_report_docx.py +++ b/backend/app/services/exporters/full_report_docx.py @@ -298,7 +298,13 @@ def _build_zouit(doc: _DocxDocument, result: dict[str, Any]) -> None: summary_pairs: list[tuple[str, Any]] = [ ("Есть ЗОУИТ", has_zouit), - ("Кол-во типов ЗОУИТ", zouit_count), + # Подпись именно «Кол-во ЗОУИТ», а не «типов»: значение приходит из + # encumbrance.zouit_count, а там `len(zouit_rows)` — число ЗАПИСЕЙ cad_zouit, + # пересёкших участок (parcels.py). Типы лежат отдельно, в zouit_types, и + # показаны строкой ниже. Прежняя подпись «Кол-во типов ЗОУИТ» расходилась со + # значением в 717 разборах из 1637 с ЗОУИТ — 43.8%, в среднем завышая «типы» + # в 1.35 раза (#2464). + ("Кол-во ЗОУИТ", zouit_count), ] if zouit_types: summary_pairs.append(("Типы", ", ".join(str(t) for t in zouit_types))) diff --git a/backend/app/services/exporters/full_report_html.py b/backend/app/services/exporters/full_report_html.py index e8d0f6ed..7ce3b393 100644 --- a/backend/app/services/exporters/full_report_html.py +++ b/backend/app/services/exporters/full_report_html.py @@ -538,7 +538,13 @@ def _build_zouit(result: dict[str, Any]) -> str: summary_pairs: list[tuple[str, Any]] = [ ("Есть ЗОУИТ", has_zouit), - ("Кол-во типов ЗОУИТ", zouit_count), + # Подпись именно «Кол-во ЗОУИТ», а не «типов»: значение приходит из + # encumbrance.zouit_count, а там `len(zouit_rows)` — число ЗАПИСЕЙ cad_zouit, + # пересёкших участок (parcels.py). Типы лежат отдельно, в zouit_types, и + # показаны строкой ниже. Прежняя подпись «Кол-во типов ЗОУИТ» расходилась со + # значением в 717 разборах из 1637 с ЗОУИТ — 43.8%, в среднем завышая «типы» + # в 1.35 раза (#2464). + ("Кол-во ЗОУИТ", zouit_count), ] if zouit_types: summary_pairs.append(("Типы", ", ".join(str(t) for t in zouit_types))) diff --git a/backend/tests/services/exporters/test_2464_zouit_count_label.py b/backend/tests/services/exporters/test_2464_zouit_count_label.py new file mode 100644 index 00000000..ec17ce91 --- /dev/null +++ b/backend/tests/services/exporters/test_2464_zouit_count_label.py @@ -0,0 +1,116 @@ +"""Подпись к числу ЗОУИТ не должна обещать «типы», когда там записи (#2464). + +`encumbrance.zouit_count` приходит из `parcels.py` как `len(zouit_rows)` — число ЗАПИСЕЙ +`cad_zouit`, пересёкших участок. Типы лежат отдельно, в `zouit_types`. Оба экспортёра +подписывали это число как «Кол-во типов ЗОУИТ». + +Расхождение не редкое: из 1637 разборов с ЗОУИТ в 717 (43.8 %) число записей не равно +числу типов, в среднем завышая «типы» в 1.35 раза (максимум по проду — 7 записей при +3 типах). + +Инвариант проверяется по смыслу, а не сверкой с выбранной строкой: **если подпись обещает +типы, показанное число обязано равняться числу типов**. Такая формулировка переживёт любое +разумное переименование и не даст «починить» тест подгонкой подписи. +""" + +from __future__ import annotations + +import os + +os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test") + +import re +from typing import Any + +import pytest + +# Пять записей ЗОУИТ, но всего два различных типа — ровно тот случай, где подпись врала. +_OVERLAPS: list[dict[str, Any]] = [ + {"type_zone": "Охранная зона ЛЭП", "name": "ВЛ-110 №1", "coverage_pct": 12.0}, + {"type_zone": "Охранная зона ЛЭП", "name": "ВЛ-110 №2", "coverage_pct": 8.0}, + {"type_zone": "Охранная зона ЛЭП", "name": "ВЛ-110 №3", "coverage_pct": 3.0}, + {"type_zone": "Приаэродромная территория", "name": "ПАТ подзона 3", "coverage_pct": 100.0}, + {"type_zone": "Приаэродромная территория", "name": "ПАТ подзона 4", "coverage_pct": 100.0}, +] +_RESULT: dict[str, Any] = { + "encumbrance": { + "has_zouit": True, + "zouit_count": 5, + "zouit_types": ["Охранная зона ЛЭП", "Приаэродромная территория"], + }, + "nspd_zouit_overlaps": _OVERLAPS, +} + +_RECORDS = 5 +_TYPES = 2 + + +def _html_pairs() -> list[tuple[str, str]]: + """KV-пары из HTML-блока ЗОУИТ: [(подпись, значение), …].""" + from app.services.exporters.full_report_html import _build_zouit + + out = _build_zouit(dict(_RESULT)) + return [ + (re.sub(r"<[^>]+>", "", k).strip(), re.sub(r"<[^>]+>", "", v).strip()) + for k, v in re.findall(r"