fix(ptica): HTML-экспортёр перестаёт выбрасывать легаси-зонирование, которое сам же проверил (#2464) #2959

Merged
bot-backend merged 1 commit from fix/2464-html-zoning-fallback into main 2026-08-20 08:44:12 +00:00
2 changed files with 96 additions and 0 deletions

View file

@ -482,6 +482,12 @@ def _build_zoning(result: dict[str, Any]) -> str:
note = zoning.get("note")
note_html = f'<p class="alt-meta">{_esc(note)}</p>' if note else ""
return _no_data() + note_html
# Без этой строки годное легаси-зонирование признавалось пригодным выше и тут же
# терялось: ниже всё читается из nspd_zoning, а он в этой ветке пустой — все пары
# выходили None, отбрасывались фильтром, и §1 печатал «нет данных» ПОВЕРХ
# имеющихся данных. Соседний full_report_docx._build_zoning делает ровно это же
# присваивание (#2464).
nspd_zoning = zoning
zone_code = nspd_zoning.get("zone_code") or nspd_zoning.get("regulation_zone_index")
pairs: list[tuple[str, Any]] = [

View file

@ -0,0 +1,90 @@
"""HTML-экспортёр не должен выбрасывать легаси-зонирование, которое сам же проверил (#2464).
`_build_zoning` при пустом `nspd_zoning` берёт легаси `result["zoning"]`, проверяет, что
там есть `zone_code`, и дальше читает поля из так и оставшегося ПУСТЫМ `nspd_zoning`.
То есть данные признаны годными и тут же потеряны: все пары получаются None, отбрасываются
фильтром, и таблица уходит в отчёт без единой строки.
Соседний экспортёр `full_report_docx._build_zoning` та же структура слово в слово эту
ветку закрывает одной строкой `nspd_zoning = zoning`. Здесь её не было.
Достижимость. Сегодня ветка не срабатывает на проде: легаси-зонирование заполняется из
`pzz_zones_ekb`, а та пуста намеренно задача `pzz-sync` отключена в beat-расписании,
потому что Росреестр закрыл PKK6 (#233, #259). Так что это латентный дефект, а не
наблюдаемый отказ. Чинится он всё равно: HTML-сборка основа полного PDF-отчёта
(`full_report_pdf` импортирует `build_full_report_html`), и в день, когда таблицу
наполнят, зонирование молча пропадёт из главного документа.
"""
from __future__ import annotations
import os
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from typing import Any
# Легаси-зонирование в той форме, в какой его собирает parcels.py из pzz_zones_ekb.
_LEGACY_ZONING: dict[str, Any] = {
"zone_code": "Ж-4",
"zone_name": "Зона многоэтажной жилой застройки",
"description": "Многоэтажные жилые дома",
"data_available": True,
"source": "rosreestr-pkk6-cached",
}
def _zoning_html(result: dict[str, Any]) -> str:
from app.services.exporters.full_report_html import _build_zoning
return _build_zoning(result)
def test_legacy_zoning_reaches_the_table() -> None:
"""Код зоны из легаси-источника обязан попасть в таблицу отчёта."""
out = _zoning_html({"nspd_zoning": None, "zoning": dict(_LEGACY_ZONING)})
assert "Ж-4" in out, (
"код зоны из легаси-зонирования не попал в таблицу: ветка признала данные "
"годными и продолжила читать из пустого nspd_zoning"
)
assert "Зона многоэтажной жилой застройки" in out, "наименование зоны потеряно"
def test_legacy_zoning_does_not_render_as_no_data() -> None:
"""Таблица с данными не должна выглядеть как «данных нет».
Отдельно от предыдущего: даже если код зоны когда-нибудь попадёт в вывод другим
путём, заглушка «нет данных» рядом с реальными данными сама по себе дефект.
"""
out = _zoning_html({"nspd_zoning": None, "zoning": dict(_LEGACY_ZONING)})
from app.services.exporters.full_report_html import _NO_DATA
assert _NO_DATA not in out, f"в таблице с данными стоит заглушка {_NO_DATA!r}"
def test_nspd_zoning_still_wins_when_present() -> None:
"""Контроль: при живом nspd_zoning легаси не подмешивается.
Ловит «починку», которая слила бы два источника: приоритет НСПД должен остаться.
"""
out = _zoning_html(
{
"nspd_zoning": {"zone_code": "ЦС-1", "zone_name": "Зона делового назначения"},
"zoning": dict(_LEGACY_ZONING),
}
)
assert "ЦС-1" in out
assert "Ж-4" not in out, "легаси-зона подмешалась поверх живого nspd_zoning"
def test_absent_zoning_still_says_no_data() -> None:
"""Контроль: когда данных нет НИГДЕ, отчёт по-прежнему честно говорит об этом."""
from app.services.exporters.full_report_html import _NO_DATA
out = _zoning_html({"nspd_zoning": None, "zoning": {"zone_code": None, "note": "PKK6 закрыт"}})
assert _NO_DATA in out, "исчезла честная заглушка «данных нет»"
assert "PKK6 закрыт" in out, "потеряна пояснительная сноска к отсутствию данных"