Files
filam3d/CLAUDE.md
2026-03-22 12:40:33 +03:00

788 lines
29 KiB
Markdown
Raw Permalink 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.
# 3D Print Calculator — Техническое задание на MVP
## Обзор проекта
Сервис 3D-печати на заказ с автоматическим расчётом стоимости. Клиент загружает 3D-модель (STL, 3MF, OBJ), выбирает материал, получает мгновенный расчёт цены и оформляет заказ. AI-ассистент помогает выбрать оптимальный материал под задачу.
**Бизнес-модель:** B2B — корпуса для электроники, функциональные запчасти, прототипы.
**Стек:** Python (FastAPI) + Vue 3 (Vite) + PostgreSQL + Nginx + Docker Compose.
**Хостинг:** VPS (Ubuntu 24), деплой через Docker Compose.
---
## Архитектура
```
┌─────────────────────────────────────────────┐
│ Nginx (reverse proxy) │
│ :80 → frontend, /api → backend │
└──────────┬──────────────┬───────────────────┘
│ │
┌──────────▼──────┐ ┌─────▼──────────────────┐
│ Vue 3 (Vite) │ │ FastAPI (Python) │
│ SPA, static │ │ REST API + WebSocket │
│ Port: 5173 │ │ Port: 8000 │
└─────────────────┘ └──────┬─────┬────────────┘
│ │
┌──────▼┐ ┌──▼───────────┐
│ PostgreSQL │ File Storage │
│ :5432 │ (local /uploads) │
└───────┘ └──────────────┘
```
### Контейнеры Docker Compose
1. **frontend** — Node 20 + Vite dev server (в проде — собранная статика через Nginx)
2. **backend** — Python 3.12 + FastAPI + Uvicorn
3. **db** — PostgreSQL 16
4. **nginx** — reverse proxy, SSL termination
---
## Backend (FastAPI)
### Структура проекта
```
backend/
├── app/
│ ├── main.py # FastAPI app, CORS, middleware
│ ├── config.py # Settings (pydantic-settings)
│ ├── database.py # SQLAlchemy async engine + session
│ ├── models/
│ │ ├── material.py # Material ORM model
│ │ ├── order.py # Order ORM model
│ │ └── file_upload.py # Uploaded file metadata
│ ├── schemas/
│ │ ├── calculate.py # Request/Response для калькулятора
│ │ ├── material.py # Material schemas
│ │ └── order.py # Order schemas
│ ├── routers/
│ │ ├── calculate.py # POST /api/calculate
│ │ ├── materials.py # GET /api/materials
│ │ ├── orders.py # POST /api/orders
│ │ └── ai_advisor.py # POST /api/advisor
│ ├── services/
│ │ ├── file_parser.py # Парсинг STL/3MF/OBJ → геометрия
│ │ ├── price_engine.py # Расчёт стоимости
│ │ ├── ai_advisor.py # Интеграция с Claude API
│ │ └── telegram_notify.py # Уведомления в Telegram
│ └── seed/
│ └── materials.py # Начальные данные по материалам
├── requirements.txt
├── Dockerfile
└── alembic/ # Миграции БД
```
### API Endpoints
#### 1. POST /api/calculate
Основной endpoint калькулятора. Принимает файл и параметры, возвращает расчёт.
**Request:** `multipart/form-data`
- `file` (File, required) — 3D-модель (STL, 3MF, OBJ). Макс. размер: 50MB.
- `material_id` (int, required) — ID выбранного материала
- `infill_percent` (int, optional, default=30) — Процент заполнения (10-100)
- `layer_height_mm` (float, optional, default=0.2) — Высота слоя (0.08-0.4)
- `quantity` (int, optional, default=1) — Количество экземпляров (1-500)
- `post_processing` (str[], optional) — Постобработка: ["sanding", "painting", "threading"]
**Response:** `application/json`
```json
{
"success": true,
"file_info": {
"filename": "case_v3.stl",
"format": "stl",
"volume_cm3": 42.7,
"surface_area_cm2": 198.3,
"bounding_box_mm": {"x": 120.0, "y": 80.0, "z": 35.0},
"is_watertight": true,
"triangle_count": 12840
},
"calculation": {
"material": {
"id": 3,
"name": "PA (Nylon)",
"density_g_cm3": 1.14,
"price_per_gram": 50.0
},
"weight_grams": 48.7,
"material_cost_rub": 2435.0,
"print_time_hours": 4.2,
"time_cost_rub": 840.0,
"post_processing_cost_rub": 500.0,
"subtotal_rub": 3775.0,
"quantity": 2,
"quantity_discount_percent": 5,
"total_rub": 7172.5,
"estimated_days": 3
}
}
```
**Ошибки:**
- 400 — Неподдерживаемый формат файла
- 400 — Файл повреждён или не является 3D-моделью
- 400 — Модель не является водонепроницаемой (warning, не blocking)
- 413 — Файл слишком большой (>50MB)
#### 2. GET /api/materials
Список доступных материалов с характеристиками.
**Response:**
```json
[
{
"id": 1,
"name": "PLA",
"category": "basic",
"price_per_gram": 25.0,
"density_g_cm3": 1.24,
"properties": {
"max_temp_c": 60,
"strength": "medium",
"flexibility": "low",
"chemical_resistance": "low",
"food_safe": true
},
"description": "Базовый пластик, подходит для прототипов и декоративных изделий",
"color_options": ["white", "black", "gray", "red", "blue", "green", "natural"]
}
]
```
#### 3. POST /api/advisor
AI-ассистент для выбора материала. Принимает описание задачи, возвращает рекомендацию.
**Request:**
```json
{
"task_description": "Нужен корпус для уличного датчика температуры. Будет стоять на улице, диапазон температур от -30 до +50. Должен быть водонепроницаемым.",
"budget_preference": "optimal",
"file_info": {
"volume_cm3": 42.7,
"bounding_box_mm": {"x": 120, "y": 80, "z": 35}
}
}
```
**Response:**
```json
{
"recommended_material_id": 4,
"recommended_material_name": "PETG",
"reasoning": "Для уличного корпуса датчика рекомендую PETG: термостойкость до +80°C, морозостойкость до -40°C, хорошая UV-стойкость и водонепроницаемость. ABS тоже подошёл бы, но PETG проще в печати и не требует закрытой камеры.",
"alternatives": [
{
"material_id": 2,
"name": "ABS",
"why": "Выше ударопрочность, но требует постобработки для герметичности"
},
{
"material_id": 5,
"name": "ASA",
"why": "Лучшая UV-стойкость, но дороже"
}
]
}
```
**Реализация:** Отправляем запрос к Claude API (модель claude-sonnet-4-20250514) с системным промптом, содержащим каталог материалов и их свойства. Ключ API хранится в переменной окружения `ANTHROPIC_API_KEY`.
#### 4. POST /api/orders
Оформление заказа.
**Request:**
```json
{
"calculation_id": "uuid-of-saved-calculation",
"client_name": "Иван Петров",
"client_phone": "+79001234567",
"client_email": "ivan@example.com",
"client_company": "ООО Технопарк",
"delivery_method": "pickup",
"comment": "Нужно к пятнице, нанести резьбу M4 в двух отверстиях"
}
```
**Response:**
```json
{
"order_id": "ORD-2026-0042",
"status": "pending",
"total_rub": 7172.5,
"estimated_ready_date": "2026-04-02"
}
```
**Side effect:** Отправляет уведомление в Telegram-бот владельца с деталями заказа.
### Сервис парсинга файлов (file_parser.py)
Используемые библиотеки:
- `trimesh` — основной парсер. Читает STL (binary + ASCII), OBJ, 3MF, PLY, GLTF.
- `numpy-stl` — запасной вариант для STL, если trimesh не справился.
Что извлекаем из файла:
```python
import trimesh
def parse_3d_file(file_path: str, file_extension: str) -> FileInfo:
"""
Парсит 3D-файл и возвращает геометрические характеристики.
Поддерживаемые форматы: .stl, .3mf, .obj
STEP-файлы (.step, .stp) — в v2 (требует cadquery/OCP).
"""
mesh = trimesh.load(file_path, file_type=file_extension)
# Если 3MF содержит несколько тел — объединяем
if isinstance(mesh, trimesh.Scene):
mesh = trimesh.util.concatenate(mesh.dump())
return FileInfo(
volume_cm3=mesh.volume / 1000, # mm³ → cm³
surface_area_cm2=mesh.area / 100, # mm² → cm²
bounding_box_mm={
"x": mesh.bounding_box.extents[0],
"y": mesh.bounding_box.extents[1],
"z": mesh.bounding_box.extents[2],
},
is_watertight=mesh.is_watertight,
triangle_count=len(mesh.faces),
)
```
### Сервис расчёта цены (price_engine.py)
```python
def calculate_price(
file_info: FileInfo,
material: Material,
infill_percent: int = 30,
layer_height_mm: float = 0.2,
quantity: int = 1,
post_processing: list[str] = [],
) -> Calculation:
"""
Формула:
1. effective_volume = volume_cm3 * (infill_percent / 100) * 0.7 + volume_cm3 * 0.3
(70% объёма масштабируется по infill, 30% — стенки, всегда 100%)
2. weight_g = effective_volume * material.density_g_cm3
3. material_cost = weight_g * material.price_per_gram
4. print_time_h = estimate_print_time(file_info, layer_height_mm, material)
5. time_cost = print_time_h * TIME_RATE_PER_HOUR # ~200 руб/час
6. post_processing_cost = sum стоимостей выбранных операций
7. subtotal = material_cost + time_cost + post_processing_cost
8. total = subtotal * quantity * (1 - volume_discount(quantity))
"""
```
**Оценка времени печати (упрощённая):**
```python
def estimate_print_time(file_info, layer_height_mm, material):
"""
Упрощённая оценка без полного слайсинга.
layers = bounding_box_z / layer_height
volume_per_layer = volume_cm3 / layers * 1000 # mm³
time_per_layer = volume_per_layer / material.flow_rate_mm3_s / 60 # минуты
travel_time_per_layer ≈ 0.3 мин (константа для Bambu Lab)
total = layers * (time_per_layer + travel_time) + setup_time
"""
```
**Скидки за количество:**
- 1 шт — 0%
- 2-5 шт — 5%
- 6-20 шт — 10%
- 21-100 шт — 15%
- 101-500 шт — 20%
**Стоимость постобработки:**
- `sanding` (шлифовка) — 300 руб/шт
- `painting` (покраска) — 500 руб/шт
- `threading` (нарезка резьбы) — 200 руб/отверстие
- `acetone_smoothing` (ацетоновая обработка, только ABS) — 400 руб/шт
### Справочник материалов (seed data)
```python
MATERIALS = [
{
"name": "PLA",
"category": "basic",
"density_g_cm3": 1.24,
"price_per_gram": 25.0,
"flow_rate_mm3_s": 15.0,
"max_temp_c": 60,
"min_temp_c": -20,
"strength": "medium",
"flexibility": "low",
"chemical_resistance": "low",
"uv_resistance": "low",
"food_safe": True,
"description": "Базовый пластик. Лёгкий в печати, хорошая детализация. Для прототипов и декора.",
},
{
"name": "PETG",
"category": "basic",
"density_g_cm3": 1.27,
"price_per_gram": 28.0,
"flow_rate_mm3_s": 12.0,
"max_temp_c": 80,
"min_temp_c": -40,
"strength": "high",
"flexibility": "medium",
"chemical_resistance": "medium",
"uv_resistance": "medium",
"food_safe": True,
"description": "Универсальный инженерный пластик. Прочный, химстойкий, подходит для улицы.",
},
{
"name": "ABS",
"category": "basic",
"density_g_cm3": 1.04,
"price_per_gram": 25.0,
"flow_rate_mm3_s": 12.0,
"max_temp_c": 100,
"min_temp_c": -30,
"strength": "high",
"flexibility": "low",
"chemical_resistance": "medium",
"uv_resistance": "low",
"food_safe": False,
"description": "Термостойкий, ударопрочный. Требует закрытой камеры. Обрабатывается ацетоном.",
},
{
"name": "PA (Nylon)",
"category": "engineering",
"density_g_cm3": 1.14,
"price_per_gram": 50.0,
"flow_rate_mm3_s": 10.0,
"max_temp_c": 120,
"min_temp_c": -40,
"strength": "very_high",
"flexibility": "medium",
"chemical_resistance": "high",
"uv_resistance": "medium",
"food_safe": False,
"description": "Инженерный пластик. Высокая прочность, износостойкость. Для шестерён, креплений.",
},
{
"name": "PC (Поликарбонат)",
"category": "engineering",
"density_g_cm3": 1.20,
"price_per_gram": 60.0,
"flow_rate_mm3_s": 8.0,
"max_temp_c": 140,
"min_temp_c": -40,
"strength": "very_high",
"flexibility": "low",
"chemical_resistance": "high",
"uv_resistance": "high",
"food_safe": False,
"description": "Максимальная термостойкость и прочность. Для корпусов, работающих при высоких температурах.",
},
{
"name": "TPU",
"category": "engineering",
"density_g_cm3": 1.21,
"price_per_gram": 40.0,
"flow_rate_mm3_s": 6.0,
"max_temp_c": 80,
"min_temp_c": -30,
"strength": "medium",
"flexibility": "very_high",
"chemical_resistance": "high",
"uv_resistance": "medium",
"food_safe": False,
"description": "Эластичный пластик, аналог резины. Для прокладок, амортизаторов, гибких деталей.",
},
{
"name": "PA-CF (Нейлон + углеволокно)",
"category": "composite",
"density_g_cm3": 1.18,
"price_per_gram": 75.0,
"flow_rate_mm3_s": 8.0,
"max_temp_c": 150,
"min_temp_c": -40,
"strength": "extreme",
"flexibility": "low",
"chemical_resistance": "very_high",
"uv_resistance": "high",
"food_safe": False,
"description": "Композит с углеволокном. Максимальная жёсткость и прочность. Замена алюминия.",
},
]
```
### AI Advisor — системный промпт
Файл `app/services/ai_advisor.py` использует Anthropic Python SDK:
```python
SYSTEM_PROMPT = """
Ты — эксперт по 3D-печати из инженерных пластиков по технологии FDM.
Твоя задача — рекомендовать оптимальный материал для печати на основе описания задачи клиента.
Доступные материалы:
{materials_json}
Правила:
1. Всегда рекомендуй один основной материал и 1-2 альтернативы.
2. Учитывай: температурный режим, механические нагрузки, химическое воздействие, UV, влажность.
3. Если задача не подходит для FDM-печати (слишком мелкие детали, высокая точность) — честно скажи об этом.
4. Отвечай кратко, по делу, на русском языке.
5. Если клиент не указал критичные параметры — задай уточняющие вопросы.
Формат ответа — строго JSON:
{
"recommended_material_id": <int>,
"reasoning": "<обоснование на русском>",
"alternatives": [{"material_id": <int>, "name": "<str>", "why": "<причина>"}],
"questions": ["<вопрос, если нужна доп. информация>"] // пустой массив если вопросов нет
}
"""
```
### Telegram-уведомления (telegram_notify.py)
При создании заказа отправляем сообщение в Telegram-бот владельца:
```python
import httpx
async def notify_new_order(order: Order):
"""Отправляет уведомление о новом заказе в Telegram."""
text = (
f"🆕 Новый заказ #{order.order_id}\n"
f"Клиент: {order.client_name}\n"
f"Телефон: {order.client_phone}\n"
f"Материал: {order.material_name}\n"
f"Сумма: {order.total_rub}\n"
f"Комментарий: {order.comment or ''}"
)
await httpx.AsyncClient().post(
f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage",
json={"chat_id": TELEGRAM_CHAT_ID, "text": text}
)
```
Переменные окружения: `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`.
---
## Frontend (Vue 3 + Vite)
### Структура проекта
```
frontend/
├── src/
│ ├── App.vue
│ ├── main.js
│ ├── router/
│ │ └── index.js # Vue Router
│ ├── stores/
│ │ ├── calculator.js # Pinia store — состояние калькулятора
│ │ └── materials.js # Pinia store — материалы
│ ├── api/
│ │ └── client.js # Axios instance, базовые запросы
│ ├── views/
│ │ ├── CalculatorView.vue # Главная страница с калькулятором
│ │ ├── MaterialsView.vue # Каталог материалов
│ │ └── OrderView.vue # Форма заказа
│ ├── components/
│ │ ├── FileUploader.vue # Drag-and-drop загрузка файла
│ │ ├── MaterialPicker.vue # Выбор материала (карточки)
│ │ ├── PrintSettings.vue # Настройки печати (infill, layer)
│ │ ├── PriceResult.vue # Отображение расчёта
│ │ ├── AiAdvisor.vue # Чат с AI-ассистентом
│ │ └── OrderForm.vue # Форма заказа
│ └── assets/
│ └── styles/
│ └── main.css # Tailwind CSS
├── index.html
├── vite.config.js
├── tailwind.config.js
├── package.json
└── Dockerfile
```
### Зависимости
```json
{
"dependencies": {
"vue": "^3.5",
"vue-router": "^4.4",
"pinia": "^2.2",
"axios": "^1.7"
},
"devDependencies": {
"vite": "^6.0",
"@vitejs/plugin-vue": "^5.1",
"tailwindcss": "^3.4",
"autoprefixer": "^10.4",
"postcss": "^8.4"
}
}
```
### Страницы и маршруты
| Путь | Компонент | Описание |
|------|-----------|----------|
| `/` | CalculatorView | Главная: загрузка файла → материал → настройки → цена |
| `/materials` | MaterialsView | Каталог материалов с фильтрами |
| `/order/:calcId` | OrderView | Форма оформления заказа |
### Компонент FileUploader.vue
Требования:
- Drag-and-drop зона + кнопка «Выбрать файл»
- Принимает: `.stl`, `.3mf`, `.obj` (валидация по расширению на фронте)
- Максимальный размер: 50 MB
- Отображает имя файла, размер и иконку формата после загрузки
- Показывает прогресс-бар при отправке на сервер
- При ошибке парсинга — человекочитаемое сообщение
### Компонент MaterialPicker.vue
Требования:
- Отображает материалы карточками (не dropdown)
- Карточка содержит: название, цену за грамм, ключевые свойства (иконки)
- Разделение на категории: «Базовые», «Инженерные», «Композитные»
- Выбранный материал подсвечивается
- Кнопка «Помочь выбрать» открывает AI-ассистент
### Компонент PrintSettings.vue
Требования:
- Слайдер: заполнение (10%-100%, шаг 10%, default 30%)
- Слайдер: высота слоя (0.08-0.4mm, шаг 0.04, default 0.2)
- Поле: количество (1-500, default 1)
- Чекбоксы: постобработка (шлифовка, покраска, резьба, ацетон)
- Подсказки при наведении: как параметр влияет на результат
### Компонент PriceResult.vue
Требования:
- Показывает разбивку: материал, время, постобработка, скидка, итого
- Крупно отображает итоговую цену
- Показывает примерный срок изготовления
- Кнопка «Оформить заказ» → переход на /order/:calcId
- Кнопка «Скачать расчёт (PDF)» — v2, пока не реализуем
### Компонент AiAdvisor.vue
Требования:
- Модальное окно или выдвижная панель справа
- Текстовое поле для описания задачи
- Кнопка «Получить рекомендацию»
- Отображает рекомендацию: основной материал + альтернативы с обоснованием
- Кнопка «Выбрать» рядом с каждой рекомендацией — применяет материал в калькулятор
### Дизайн
- Стиль: минималистичный, светлая тема, Tailwind CSS
- Акцентный цвет: #2563EB (синий) — кнопки, выделения
- Шрифт: Inter (Google Fonts)
- Адаптивность: mobile-first, работает на телефоне
- Тёмная тема: v2 (не в MVP)
---
## База данных (PostgreSQL)
### Таблицы
**materials**
```sql
CREATE TABLE materials (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(50) NOT NULL, -- basic, engineering, composite
density_g_cm3 FLOAT NOT NULL,
price_per_gram FLOAT NOT NULL,
flow_rate_mm3_s FLOAT NOT NULL,
max_temp_c INT,
min_temp_c INT,
strength VARCHAR(20), -- low, medium, high, very_high, extreme
flexibility VARCHAR(20),
chemical_resistance VARCHAR(20),
uv_resistance VARCHAR(20),
food_safe BOOLEAN DEFAULT FALSE,
description TEXT,
color_options JSONB DEFAULT '[]',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW()
);
```
**calculations**
```sql
CREATE TABLE calculations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_name VARCHAR(255) NOT NULL,
file_format VARCHAR(10) NOT NULL,
file_path VARCHAR(500),
volume_cm3 FLOAT NOT NULL,
surface_area_cm2 FLOAT,
bounding_box JSONB,
is_watertight BOOLEAN,
triangle_count INT,
material_id INT REFERENCES materials(id),
infill_percent INT DEFAULT 30,
layer_height_mm FLOAT DEFAULT 0.2,
quantity INT DEFAULT 1,
post_processing JSONB DEFAULT '[]',
weight_grams FLOAT,
material_cost_rub FLOAT,
time_cost_rub FLOAT,
post_processing_cost_rub FLOAT,
total_rub FLOAT,
estimated_days INT,
created_at TIMESTAMP DEFAULT NOW()
);
```
**orders**
```sql
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
order_id VARCHAR(20) UNIQUE NOT NULL, -- ORD-2026-0001
calculation_id UUID REFERENCES calculations(id),
client_name VARCHAR(200) NOT NULL,
client_phone VARCHAR(20) NOT NULL,
client_email VARCHAR(200),
client_company VARCHAR(200),
delivery_method VARCHAR(50) DEFAULT 'pickup', -- pickup, delivery
comment TEXT,
status VARCHAR(30) DEFAULT 'pending', -- pending, confirmed, printing, ready, delivered, cancelled
total_rub FLOAT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
```
---
## Docker Compose
```yaml
version: "3.8"
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: print3d
POSTGRES_USER: print3d
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
backend:
build: ./backend
environment:
DATABASE_URL: postgresql+asyncpg://print3d:${DB_PASSWORD}@db:5432/print3d
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID}
UPLOAD_DIR: /app/uploads
volumes:
- uploads:/app/uploads
depends_on:
- db
ports:
- "8000:8000"
frontend:
build: ./frontend
ports:
- "5173:5173"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/certs:/etc/nginx/certs
depends_on:
- backend
- frontend
volumes:
pgdata:
uploads:
```
---
## Переменные окружения (.env)
```
DB_PASSWORD=<strong_password>
ANTHROPIC_API_KEY=<claude_api_key>
TELEGRAM_BOT_TOKEN=<telegram_bot_token>
TELEGRAM_CHAT_ID=<your_telegram_chat_id>
```
---
## Порядок реализации
### Фаза 1 — Backend core (3-4 дня)
1. Инициализация FastAPI проекта, Docker, PostgreSQL
2. Модели SQLAlchemy + Alembic миграции
3. Seed данных по материалам
4. Сервис парсинга файлов (trimesh)
5. Сервис расчёта цены (price engine)
6. Endpoints: POST /api/calculate, GET /api/materials
### Фаза 2 — Frontend core (3-4 дня)
1. Инициализация Vue 3 + Vite + Tailwind
2. FileUploader компонент
3. MaterialPicker компонент
4. PrintSettings + PriceResult компоненты
5. Интеграция с API (Pinia stores + Axios)
### Фаза 3 — AI + Orders (2-3 дня)
1. AI Advisor — интеграция Claude API
2. AiAdvisor компонент (фронт)
3. OrderForm компонент + POST /api/orders
4. Telegram-уведомления
### Фаза 4 — Деплой (1-2 дня)
1. Docker Compose конфигурация
2. Nginx конфиг (reverse proxy + SSL)
3. Деплой на VPS
4. Тестирование end-to-end
---
## Что НЕ входит в MVP (v2)
- 3D-превью модели в браузере (Three.js + STLLoader)
- Парсинг STEP-файлов (требует cadquery / OpenCASCADE)
- Личный кабинет клиента
- Онлайн-оплата (ЮKassa / Stripe)
- История заказов
- Тёмная тема
- Скачивание расчёта в PDF
- Мультиязычность
- SEO-оптимизация (SSR / pre-rendering)