gendesign/backend/tests/services/generative/test_exporters.py
Light1YT 8242e51b61
Some checks failed
CI / frontend-tests (push) Has been skipped
CI / changes (push) Successful in 10s
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 7m39s
CI / backend-tests (pull_request) Successful in 7m39s
Deploy / deploy (push) Has been cancelled
Deploy / build-frontend (push) Has been cancelled
Deploy / build-backend (push) Has been cancelled
Deploy / build-worker (push) Has been cancelled
Deploy / changes (push) Successful in 6s
feat(generative): exporters (dxf/pdf) + generative test suite (#54 #55 #56)
Дополняет движок: exporters/{dxf,pdf}.py (ezdxf + WeasyPrint lazy) + 5 тест-модулей
(geometry/placement/teap_financial/exporters/api). Не вошли в предыдущий commit
(untracked-директории).
2026-06-13 21:33:20 +05:00

127 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Stage 1c tests — DXF and PDF exporters.
DXF is asserted by a binary round-trip (re-read with ezdxf, check layers/entities).
The PDF render is skipped when WeasyPrint's native libraries are unavailable (dev
boxes без libgobject); the HTML-build step is always exercised.
"""
from __future__ import annotations
import importlib.util
import os
import tempfile
from collections import Counter
import ezdxf
import pytest
from app.schemas.concept import ConceptInput
from app.services.generative import geometry
from app.services.generative.exporters import dxf, pdf
_PARCEL_COORDS = [
[60.60, 56.830],
[60.6045, 56.830],
[60.6045, 56.8328],
[60.60, 56.8328],
[60.60, 56.830],
]
def _payload() -> ConceptInput:
return ConceptInput(
parcel_geojson={"type": "Polygon", "coordinates": [_PARCEL_COORDS]},
housing_class="comfort",
target_floors=9,
development_type="mid_rise",
land_cost_rub=150_000_000.0,
)
def _weasyprint_available() -> bool:
"""WeasyPrint импортируется только с нативными библиотеками (libgobject и т.д.)."""
if importlib.util.find_spec("weasyprint") is None:
return False
try:
import weasyprint # noqa: F401
except OSError:
return False
return True
def test_dxf_export_round_trips_with_layers() -> None:
payload = _payload()
parcel = geometry.parse_parcel(payload)
variant = geometry.generate(payload)[0]
data = dxf.export_concept_dxf(parcel, variant)
assert data.startswith(b"AutoCAD Binary DXF")
with tempfile.NamedTemporaryFile(suffix=".dxf", delete=False) as fh:
fh.write(data)
path = fh.name
try:
doc = ezdxf.readfile(path)
finally:
os.unlink(path)
msp = doc.modelspace()
by_layer = Counter(e.dxf.layer for e in msp)
# Участок и пятно застройки нарисованы.
assert by_layer["PARCEL"] == 1
assert by_layer["BUILDABLE"] == 1
# Секции нарисованы (по одному polyline на секцию + подписи).
n_sections = len(variant.buildings_geojson["features"])
assert n_sections > 0
assert by_layer["BUILDINGS"] >= n_sections
def test_dxf_building_footprints_have_metric_area() -> None:
from shapely.geometry import Polygon
payload = _payload()
parcel = geometry.parse_parcel(payload)
variant = geometry.generate(payload)[0]
data = dxf.export_concept_dxf(parcel, variant)
with tempfile.NamedTemporaryFile(suffix=".dxf", delete=False) as fh:
fh.write(data)
path = fh.name
try:
doc = ezdxf.readfile(path)
finally:
os.unlink(path)
areas = []
for e in doc.modelspace():
if e.dxftype() == "LWPOLYLINE" and e.dxf.layer == "BUILDINGS":
pts = [(p[0], p[1]) for p in e.get_points()]
areas.append(Polygon(pts).area)
assert areas, "no building polylines found"
# Площади секций — десятки/сотни кв.м (метры), не доли (градусы).
for area in areas:
assert 50.0 < area < 2000.0
def test_pdf_html_build_contains_tables() -> None:
variants = geometry.generate(_payload())
html = pdf._build_html(variants)
assert "Технико-экономические показатели" in html
assert "Финансовая модель" in html
assert "IRR-proxy" in html
def test_pdf_html_build_graceful_on_empty() -> None:
html = pdf._build_html([])
assert "нет вариантов" in html
@pytest.mark.skipif(
not _weasyprint_available(),
reason="WeasyPrint native libs unavailable on this host",
)
def test_pdf_export_produces_pdf_bytes() -> None:
variants = geometry.generate(_payload())
data = pdf.export_concept_pdf(variants)
assert data.startswith(b"%PDF-")
assert len(data) > 1000