init
This commit is contained in:
787
CLAUDE.md
Normal file
787
CLAUDE.md
Normal file
@@ -0,0 +1,787 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user