gendesign/tradein-mvp/backend/pyproject.toml
bot-backend 5494fa23e3
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 54s
fix(tradein/pdf): честный embedded-шрифт (PT Sans/PT Serif) + реальный matplotlib-график вместо div
Корень жалоб «дизайн не премиум»/«бары самодельные» — не в WeasyPrint, а в
execution gap: CSS объявлял font-family DejaVu Serif/Sans, но ни один
@font-face не embedded — документ всегда рендерился на системном
fallback-шрифте. Price-range bar был буквально <div> с фиксированной
64%/14% заливкой, никак не связанной с реальными числами диапазона.

Подтверждено 4 параллельными прототипами (ReportLab/Typst/Playwright/
улучшенный WeasyPrint) на одном и том же образце: WeasyPrint с честным
шрифтом и настоящим графиком визуально не уступает более дорогим
альтернативам (Chromium, Typst) при нулевом новом риске в Docker.

Изменения:
- PT Sans (400/700) + PT Serif (700, только для заголовков секций) —
  base64 @font-face из локальных .ttf (app/services/exporters/fonts/,
  OFL-лицензия ParaType), embedded один раз при импорте модуля.
- Обязательный WeasyPrint FontConfiguration() — без него @font-face
  молча игнорируется и рендер откатывается на DejaVu (реальный баг,
  найденный и исправленный в процессе).
- _price_range_bar (декоративный div) → _price_range_chart_svg —
  matplotlib (Agg backend, объектный Figure() API, не pyplot — потокобезопасно
  для конкурентных PDF-запросов в FastAPI threadpool) bullet-chart на
  реальном диапазоне значений, embedded как SVG data URI.
- url_fetcher теперь передаётся в HTML() и CSS() конструкторы, а не в
  write_pdf() — в WeasyPrint 69 write_pdf(url_fetcher=...) молча
  игнорируется, из-за чего SSRF-guard фактически не применялся к
  CSS-ресурсам (@font-face src). Побочная находка при верификации, не
  связанная с шрифтами/графиком напрямую, но тот же вызов кода.
2026-07-04 17:33:54 +03:00

60 lines
2.6 KiB
TOML
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.

[project]
name = "tradein-mvp-backend"
version = "0.1.0"
description = "Trade-In Estimator MVP — standalone fork of gendesign trade-in feature"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115.0",
"uvicorn[standard]>=0.30.0",
"sqlalchemy>=2.0.30",
"geoalchemy2>=0.15.0", # PostGIS support для SQLAlchemy
"pydantic>=2.7.0",
"pydantic-settings>=2.3.0",
"psycopg[binary]>=3.2.0",
"Pillow>=10.3.0", # photo sanitization re-encode (#6 audit fix)
"weasyprint>=62.0",
"jinja2>=3.1.0",
"httpx[socks]>=0.27.0", # для geocoder + scrapers; [socks] тянет socksio для socks5://
"tenacity>=9.0.0", # retry с exp backoff
"selectolax>=0.3.0", # быстрый HTML парсинг для scrapers
"lxml>=5.0.0", # стриминговый iterparse для многогигабайтных ГАР XML (#143)
"segno>=1.6.0", # QR-код для PDF shareable URL
"matplotlib>=3.9.0", # price-range chart (SVG) для PDF отчёта Trade-In
"curl-cffi>=0.7.0", # impersonate=chrome120 для Cian/Avito (TLS fingerprint)
"python-multipart>=0.0.9", # FastAPI UploadFile — загрузка фото квартиры (#394)
"sentry-sdk>=2.0.0", # мониторинг ошибок → GlitchTip (#396)
"redis>=5.0.0", # async hot cache для /api/v1/search (Phase 3.2)
"pyyaml>=6.0.0", # RBAC roles.yaml loader (app/core/auth.py)
"playwright>=1.45", # Playwright client для connect к tradein-browser (#905)
"scraper-kit", # internal workspace-package (#2137) — общие утилиты скрапперов;
# резолвится из workspace (см. [tool.uv.sources]), не с PyPI.
# Docker-context = tradein-mvp root, uv sync ставит editable.
]
[tool.uv.sources]
scraper-kit = { workspace = true }
[dependency-groups]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.24.0",
"pytest-timeout>=2.3.0",
"ruff>=0.5.0",
]
[tool.pytest.ini_options]
asyncio_mode = "auto"
# Страховка от зависших тестов в CI (per-test wall-clock limit, секунды).
# Покрывает и pre-merge гейт (ci-tradein.yml), и post-merge test-job (deploy).
timeout = 120
[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"]
ignore = ["RUF001", "RUF002", "RUF003"]