788 lines
29 KiB
Markdown
788 lines
29 KiB
Markdown
# 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)
|