[project] name = "gendesign-backend" version = "0.1.0" description = "Generative Design + Site Finder backend (FastAPI)" requires-python = ">=3.12" dependencies = [ "fastapi>=0.115.0", "uvicorn[standard]>=0.30.0", "sqlalchemy>=2.0.30", "geoalchemy2>=0.15.0", "alembic>=1.13.0", "pydantic>=2.7.0", "pydantic-settings>=2.3.0", "psycopg[binary]>=3.2.0", "shapely>=2.0.0", "geopandas>=1.0.0", "pyproj>=3.6.0", "httpx>=0.27.0", "redis>=5.0.0", "celery>=5.4.0", "playwright>=1.45.0", "tenacity>=9.0.0", "pillow>=10.4.0", "weasyprint>=62.0", "jinja2>=3.1.0", "ezdxf>=1.3.0", "openpyxl>=3.1.0", "python-docx>=1.1.0", # DOCX export (import name `docx`) — §22 forecast report (#959) "python-pptx>=1.0.0", # PPTX export (import name `pptx`) — §22 forecast report (#959) "pandas>=2.2.0", "numpy>=2.0.0", "scikit-learn>=1.5.0", "sentry-sdk[fastapi,celery,sqlalchemy,httpx]>=2.18.0", "rosreestr2coord>=5.0.0", "ijson>=3.2.0", "pyyaml>=6.0.0", # RBAC roles.yaml loader (app/core/auth.py) "pdfplumber>=0.10.0", # PDF-парсер постановлений об изъятии/резервировании (#1091) "beautifulsoup4>=4.12.0", # HTML-парсер реестра КРТ-площадок ЕКБ (#1060) "pymupdf>=1.24.0", # растеризация PDF→image (без poppler) для OCR (#1062) "pytesseract>=0.3.13", # OCR сканов через Tesseract для изъятия ЕКБ (#1062) "contextily>=1.7.0", # OSM basemap-тайлы для серверного рендера карт отчёта (#2259 PR-C) "matplotlib>=3.11.0", # headless (Agg) рендер PNG-карт участка/концепции (#2259 PR-C) ] [dependency-groups] dev = [ "pytest>=8.0.0", "pytest-asyncio>=0.23.0", "pytest-cov>=5.0.0", # coverage gate в CI (#68): pytest --cov=app --cov-fail-under "ruff>=0.5.0", "mypy>=1.10.0", "types-redis>=4.6.0", "pre-commit>=3.7.0", ] [tool.uv] package = true # Tell setuptools which directory IS the Python package. # Without this, having both `app/`, `alembic/`, `db/` at top level confuses # auto-discovery: "Multiple top-level packages discovered in a flat-layout". # We only want to package `app/`. `alembic/` and `db/` are operational, not Python packages. [tool.setuptools.packages.find] include = ["app*"] [tool.ruff] target-version = "py312" line-length = 100 [tool.ruff.lint] select = ["E", "F", "I", "B", "UP", "N", "RUF", "ASYNC"] # RUF001/002/003 — ambiguous Unicode (Cyrillic vs Latin lookalikes). # Off because the project is Russian-language; comments/docstrings legitimately use Cyrillic. ignore = ["RUF001", "RUF002", "RUF003"] [tool.mypy] python_version = "3.12" strict_optional = true warn_unused_ignores = true # strict только для core-модулей (правило из памяти Validation_Findings_Apr25) [[tool.mypy.overrides]] module = [ "app.services.generative.geometry", "app.services.generative.placement", "app.services.generative.teap", "app.services.generative.financial", "app.services.generative.exporters.dxf", "app.services.generative.exporters.pdf", "app.services.site_finder.scorer", ] strict = true # Геометрия/экспорт-библиотеки без type stubs (shapely/ezdxf/weasyprint не несут # py.typed) — игнорируем missing-imports, чтобы strict-модули generative проходили. # geopandas/matplotlib/contextily добавлены для report_maps (#2259 PR-C, серверный # рендер карт отчёта) — те же py.typed-less библиотеки. [[tool.mypy.overrides]] module = [ "shapely.*", "ezdxf.*", "weasyprint.*", "geopandas.*", "matplotlib.*", "contextily.*", ] ignore_missing_imports = true [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" # --cov НАМЕРЕННО НЕ в addopts: локальный `pytest` остаётся быстрым/без coverage. # Coverage-gate включается ТОЛЬКО в CI явными флагами # (`--cov=app --cov-fail-under=65`, см. .forgejo/workflows/ci.yml backend-tests). addopts = ["-m", "not prod_smoke"] markers = [ "slow: marks tests as slow (need real network, deselect with -m 'not slow')", "prod_smoke: production smoke tests against live https://gendsgn.ru (run only post-deploy with -m prod_smoke)", "integration: phantom column gate tests requiring TEST_DATABASE_URL (SSH tunnel to prod Postgres)", ] [tool.coverage.run] # Меряем покрытие пакета app/. branch=true — учитываем непокрытые ветки, не только строки. source = ["app"] branch = true omit = [ "app/**/__main__.py", "tests/*", ] [tool.coverage.report] # Baseline gate = 65% (измерено: mock-lane CI-сьют даёт ~71% на 2026-06; 65 = floor # с запасом под libpango-skip дельту и PR-to-PR дрейф, не ratchet'ит слишком туго). # Поднимать постепенно по мере роста сьюта (#68 acceptance: "постепенно поднимать"). fail_under = 65 show_missing = true skip_covered = true exclude_also = [ "if TYPE_CHECKING:", "raise NotImplementedError", "if __name__ == .__main__.:", "@(abc\\.)?abstractmethod", ]