Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
ed13f0af31 fix(ptica): HTML-экспортёр перестаёт выбрасывать легаси-зонирование, которое сам же проверил (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m57s
CI / backend-tests (pull_request) Successful in 17m20s
_build_zoning при пустом nspd_zoning берёт легаси result["zoning"], проверяет, что
там есть zone_code, — и дальше читает поля из так и оставшегося ПУСТЫМ nspd_zoning.
Все пары выходят None, отбрасываются фильтром, и §1 печатает «нет данных» ПОВЕРХ
имеющихся данных. То есть это не пустая таблица, а ложное утверждение об отсутствии.

Соседний full_report_docx._build_zoning — та же структура слово в слово — закрывает
эту ветку одной строкой `nspd_zoning = zoning`. В HTML её не было.

Достижимость называю честно: сегодня ветка на проде не срабатывает. Легаси-зонирование
заполняется из pzz_zones_ekb, а она пуста намеренно — задача pzz-sync отключена в
beat-расписании, потому что Росреестр закрыл PKK6 (#233, #259). Дефект латентный.

Чиню всё равно по двум причинам. HTML-сборка — основа полного PDF-отчёта
(full_report_pdf импортирует build_full_report_html), так что в день, когда таблицу
наполнят, зонирование молча пропадёт из главного документа. И правка — ровно одна
строка, приводящая функцию к её же соседу.

Тест против origin/main:

  код зоны Ж-4 не попал в таблицу        — вывод '<div class="no-data">нет данных</div>'
  заглушка «нет данных» рядом с данными  — то же самое, отдельным утверждением
  приоритет nspd_zoning сохранён         — контроль, зелёный на обеих сторонах
  отсутствие данных честно названо       — контроль, зелёный на обеих сторонах

Контроль «приоритет nspd_zoning» стоит не для симметрии: он ловит «починку», которая
слила бы два источника вместо того, чтобы оставить НСПД главным.

Прогоны: tests/services/exporters 224 passed rc=0; tests/services rc=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 13:23:07 +05: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, "потеряна пояснительная сноска к отсутствию данных"