Заменяет generative stubs детерминированным end-to-end пайплайном: - 1a geometry: WGS84-parse, метрическая AEQD-репроекция, setback-буфер, placement grid - 1b placement: greedy section-fill + STRtree коллизии (full-gap), 3 стратегии, coverage cap - 1c teap/financial: площади/квартиры/FAR/паркинг; выручка/затраты/маржа + IRR-proxy - exporters: ezdxf DXF (геометрия) + WeasyPrint ТЭП/фин PDF (lazy import) generate() заменил generate_stub в route (422 на degenerate). mypy-strict + ruff clean, 31 generative-тест + full suite 3078 passed. ConceptVariant заполняется реальными числами. Closes #54 Closes #55 Closes #56
30 lines
1.1 KiB
Python
30 lines
1.1 KiB
Python
import logging
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
from app.schemas.concept import ConceptInput, ConceptOutput
|
|
from app.services.generative import geometry
|
|
from app.services.generative.geometry import ParcelGeometryError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("", response_model=ConceptOutput)
|
|
async def create_concept(payload: ConceptInput) -> ConceptOutput:
|
|
"""Generate 3 building variants for the given parcel polygon.
|
|
|
|
Stage 1a: Shapely parse + buildable area (setback) + placement grid.
|
|
Stage 1b: greedy section placement with STRtree collisions (3 strategies).
|
|
Stage 1c: real ТЭП + financial model attached to each variant.
|
|
|
|
A degenerate parcel (setback consumes everything, malformed geometry) yields a
|
|
422 rather than empty variants — that is a bad request, not a valid empty result.
|
|
"""
|
|
try:
|
|
variants = geometry.generate(payload)
|
|
except ParcelGeometryError as exc:
|
|
logger.warning("concept generation rejected parcel: %s", exc)
|
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
return ConceptOutput(variants=variants)
|