- ✅ Вызов n8n webhook после выбора типа события - ✅ Формирование title из event_type + voucher - ✅ Передача всех данных: claim_id, contact_id, project_id, event_type - ✅ Сохранение ticket_id и ticket_number в formData - ✅ Loading состояние кнопки - ✅ Debug события для отслеживания
551 lines
24 KiB
Markdown
551 lines
24 KiB
Markdown
# 📅 Хронология проекта ERV Platform
|
||
|
||
**Период:** 24 октября - 1 ноября 2025
|
||
**Всего сессий:** 8
|
||
**Всего строк документации:** 5295+
|
||
**Коммитов:** 50+
|
||
|
||
---
|
||
|
||
## 📊 День 1: 24 октября 2025 - Инициализация проекта
|
||
|
||
**Задача:** Развернуть новую платформу обработки страховых обращений
|
||
|
||
### Что сделано:
|
||
- ✅ Создана структура проекта: Backend (FastAPI) + Frontend (React TypeScript)
|
||
- ✅ Подключены внешние сервисы:
|
||
- PostgreSQL (147.45.189.234:5432)
|
||
- Redis (crm.clientright.ru:6379)
|
||
- RabbitMQ (185.197.75.249:5672)
|
||
- S3 Timeweb Storage
|
||
- ✅ API endpoints: SMS, Claims, Policy, Upload
|
||
- ✅ OCR интеграция (147.45.146.17:8001)
|
||
- ✅ Базовая форма заявки (3 шага)
|
||
- ✅ Docker контейнеры: frontend, backend, postgres, redis
|
||
|
||
**Логи:** `SESSION_LOG_2025-10-24.md` (708 строк)
|
||
|
||
---
|
||
|
||
## 📊 День 2: 26 октября 2025 - N8N интеграция
|
||
|
||
**Задача:** Интеграция с n8n для асинхронной обработки файлов
|
||
|
||
### Что сделано:
|
||
- ✅ **N8N Webhooks:**
|
||
- Проверка полиса в MySQL + запись в PostgreSQL
|
||
- Загрузка файлов в S3 + OCR + Vision AI
|
||
- ✅ **React Frontend:**
|
||
- Автогенерация `claim_id` и `session_id`
|
||
- Конвертация файлов в PDF на клиенте (jsPDF + browser-image-compression)
|
||
- Поддержка форматов: JPG, PNG, HEIC, WEBP, PDF
|
||
- Сжатие изображений до 2MB
|
||
- ✅ **PostgreSQL:**
|
||
- Таблицы `claims` и `claim_files`
|
||
- UPSERT операции
|
||
- JSONB поля для гибкого хранения
|
||
- ✅ **Документация:**
|
||
- `N8N_INTEGRATION.md`
|
||
- `N8N_SQL_QUERIES.md`
|
||
- `N8N_PDF_COMPRESS.md`
|
||
|
||
**Проблемы решены:**
|
||
- Конфликт зависимостей `aioboto3` → удалён
|
||
- Nullable поля в PostgreSQL
|
||
- JSON сериализация в n8n
|
||
|
||
**Логи:** `SESSION_LOG_2025-10-26.md` (932 строки)
|
||
|
||
---
|
||
|
||
## 📊 День 3: 27 октября 2025 - SSE + Redis Pub/Sub
|
||
|
||
**Задача:** Real-time обработка документов через SSE
|
||
|
||
### Что сделано:
|
||
- ✅ **Backend SSE Endpoint** (`/events/{task_id}`):
|
||
- Server-Sent Events для real-time стриминга
|
||
- Подписка на Redis Pub/Sub канал `ocr_events:{task_id}`
|
||
- Обработка вложенного формата от n8n Redis ноды
|
||
- Автозакрытие соединения после результата
|
||
- ✅ **Frontend SSE Client:**
|
||
- Блокирующая модалка с loading spinner
|
||
- Автоматическое подключение после загрузки файла
|
||
- Отображение результатов AI анализа
|
||
- Логирование в Debug Panel
|
||
- ✅ **Vite Proxy:**
|
||
- `/events` → `host.docker.internal:8100`
|
||
- Обход файрвола (порт 8100 закрыт)
|
||
- ✅ **Docker:**
|
||
- Frontend в dev режиме (hot reload)
|
||
- `host.docker.internal` для доступа к хосту
|
||
|
||
**Проблемы решены:**
|
||
- Неправильный порт SSE → Vite proxy
|
||
- Backend к локальному Redis → абсолютный путь `.env`
|
||
- AttributeError decode → убран `.decode()`
|
||
- Префикс `/api/v1` → убран для events
|
||
- Frontend не ждёт SSE → модалка + `waitingForOcr` state
|
||
|
||
**Тестирование:**
|
||
- Время обработки: ~55 секунд
|
||
- OCR: полис E1000-302545808
|
||
- AI: извлечены ФИО, даты, программа
|
||
- Результат в модалке ✅
|
||
|
||
**Логи:** `SESSION_LOG_2025-10-27.md` (не найден в списке, но упоминается)
|
||
|
||
---
|
||
|
||
## 📊 День 4: 28 октября 2025 - Исправления SSE
|
||
|
||
**Сессия 1 (00:00-01:00):** Исправление SSE error handling
|
||
|
||
### Проблема:
|
||
После успешного распознавания показывалась ошибка "Ошибка подключения к серверу"
|
||
|
||
### Причина:
|
||
Backend закрывал SSE после отправки события → браузер триггерил `onerror` → фронтенд затирал успешный результат
|
||
|
||
### Решение:
|
||
```typescript
|
||
eventSource.onerror = (error) => {
|
||
setOcrModalContent((prev) => {
|
||
if (prev && prev !== 'loading') {
|
||
return prev; // НЕ затираем результат
|
||
}
|
||
return { success: false, message: 'Ошибка подключения к серверу' };
|
||
});
|
||
};
|
||
```
|
||
|
||
**Сессия 2 (13:00-17:00):** Умная форма Step 2
|
||
|
||
### Что сделано:
|
||
- ✅ **Рефакторинг Step 2** с AI-обработкой документов
|
||
- ✅ **Пошаговая загрузка** с модалками
|
||
- ✅ **DEV MODE кнопки** во всех 3 шагах
|
||
- ✅ **PostgreSQL UPSERT** с CTE
|
||
- ✅ **Конфигурация документов** для каждого типа события
|
||
|
||
**Логи:** `SESSION_LOG_2025-10-28.md` (1063 строки)
|
||
|
||
---
|
||
|
||
## 📊 День 5: 29 октября 2025 - Динамический визард
|
||
|
||
**Сессия 1 (12:00-15:00):** Рефакторинг визарда
|
||
|
||
### Задача:
|
||
Переделать визард так, чтобы каждый документ был отдельным шагом
|
||
|
||
### Было:
|
||
```
|
||
[1. Полис] → [2. Детали + все документы] → [3. Оплата]
|
||
```
|
||
|
||
### Стало:
|
||
```
|
||
[1. Полис] → [2. Тип] → [3. Док 1] → [4. Док 2] → ... → [N. Оплата]
|
||
```
|
||
|
||
### Что сделано:
|
||
- ✅ `Step2EventType.tsx` - выбор типа страхового случая
|
||
- ✅ `StepDocumentUpload.tsx` - загрузка каждого документа
|
||
- ✅ Динамическое количество шагов
|
||
- ✅ Прогресс-бар с реальными шагами
|
||
- ✅ SSE для каждого документа с уникальным `event_type`
|
||
|
||
**Проблемы решены:**
|
||
- Синтаксические ошибки (дублирующийся код)
|
||
- PostgreSQL INSERT не возвращал данные
|
||
- `file_size` как строка вместо числа
|
||
|
||
**Логи:** `SESSION_LOG_2025-10-29.md` (645 строк)
|
||
|
||
---
|
||
|
||
**Сессия 2 (16:30-17:30):** Безопасность N8N Webhooks
|
||
|
||
### Задача:
|
||
> "как нам не палить вебхук, а то его видно через код?"
|
||
|
||
### Проблема:
|
||
N8N webhook URLs были захардкожены в коде фронтенда → видны в DevTools
|
||
|
||
### Решение:
|
||
Backend Proxy - фронтенд больше не знает про n8n!
|
||
|
||
### Архитектура:
|
||
```
|
||
Frontend → fetch('/api/n8n/*') → Backend Proxy → N8N (URLs в .env)
|
||
```
|
||
|
||
### Что сделано:
|
||
- ✅ `backend/app/api/n8n_proxy.py` - proxy router
|
||
- ✅ Webhook URLs в `.env` (не коммитятся)
|
||
- ✅ Frontend использует `/api/n8n/policy/check` и `/api/n8n/upload/file`
|
||
- ✅ Документация `SECURITY_N8N_PROXY.md`
|
||
|
||
**Проблемы решены:**
|
||
- "Ошибка соединения" → относительные пути вместо localhost
|
||
- Пропущенные поля → добавлены `filename` и `upload_timestamp`
|
||
- event_type не совпадает → гибкая проверка
|
||
|
||
**Логи:** `SESSION_LOG_2025-10-29_part2.md` (627 строк)
|
||
|
||
---
|
||
|
||
## 📊 День 6: 30 октября 2025 - Телефон на Step 1 + CRM
|
||
|
||
**Задача:** Перенос телефона на первый шаг и создание контакта в CRM
|
||
|
||
### Что сделано:
|
||
- ✅ **Новый Step1Phone** (вместо Step1Policy):
|
||
- Ввод телефона как первый шаг
|
||
- SMS верификация
|
||
- Автосоздание контакта в CRM через n8n webhook
|
||
- ✅ **CreateWebContact** - операция vTiger webservice:
|
||
- Создание контакта по телефону
|
||
- Проверка дубликатов
|
||
- Возврат `contact_id`
|
||
- ✅ **N8N Webhook** для создания контакта после SMS
|
||
- ✅ **Формат телефона без +** (79001234567)
|
||
- ✅ **DEV MODE** кнопки на всех шагах
|
||
|
||
**Рефакторинг структуры:**
|
||
```
|
||
Было: Step1Policy → Step2Details → Step3Payment
|
||
Стало: Step1Phone → Step2Policy → Step3EventType → Step4DocUpload... → StepNPayment
|
||
```
|
||
|
||
**Оптимизация Docker:**
|
||
- Убраны локальные контейнеры Postgres и Redis
|
||
- Используются только внешние сервисы
|
||
- Остались: frontend + backend
|
||
|
||
**Логи:** `SESSION_LOG_2025-10-30.md` (597 строк)
|
||
|
||
---
|
||
|
||
## 📊 День 7: 1 ноября 2025 - CreateWebProject + Финальная интеграция
|
||
|
||
**Задача:** Создание проектов в CRM по номеру полиса
|
||
|
||
### Что сделано:
|
||
- ✅ **CreateWebProject.php** - операция vTiger:
|
||
- Поиск проекта по полису (cf_1885)
|
||
- Создание нового если не найден
|
||
- Привязка к контакту
|
||
- Возврат `{"project_id": "123", "is_new": true/false}`
|
||
- ✅ **Регистрация в БД** vTiger webservice
|
||
- ✅ **Тестирование:**
|
||
- Создание нового: `{"project_id":"396865","is_new":true}` ✅
|
||
- Повторный вызов: `{"project_id":"396865","is_new":false}` ✅
|
||
- ✅ **N8N Webhook URLs** в docker-compose.yml (environment)
|
||
|
||
**Проблемы решены:**
|
||
- Формат телефона в vTiger (mobile без +)
|
||
- SMS коды не проходили валидацию
|
||
- N8N webhook URLs не передавались в backend контейнер
|
||
|
||
**Логи:** `SESSION_LOG_2025-11-01.md` (723 строки)
|
||
|
||
---
|
||
|
||
## 🎯 Текущая архитектура (финальная)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ USER BROWSER │
|
||
│ http://147.45.146.17:5173 │
|
||
│ │
|
||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||
│ │ React Frontend (Vite Dev Server) │ │
|
||
│ │ - Step1Phone.tsx (SMS верификация) │ │
|
||
│ │ - Step2Policy.tsx (проверка полиса) │ │
|
||
│ │ - Step3EventType.tsx (тип события) │ │
|
||
│ │ - Step4-N DocumentUpload.tsx (документы) │ │
|
||
│ │ - StepNPayment.tsx (оплата) │ │
|
||
│ │ - SSE Client для real-time обновлений │ │
|
||
│ │ - PDF конвертер (HEIC/JPG/PNG → PDF) │ │
|
||
│ └────────────┬───────────────────────────────────────────────────┘ │
|
||
│ │ Vite Proxy (/api, /events → backend) │
|
||
└───────────────┼──────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ BACKEND (FastAPI, port 8100) │
|
||
│ PID: 31571 (запущен вне Docker) │
|
||
│ │
|
||
│ 📁 app/api/ │
|
||
│ ├── sms.py - SMS верификация (SigmaSMS) │
|
||
│ ├── claims.py - Заявки │
|
||
│ ├── policy.py - Проверка полисов │
|
||
│ ├── upload.py - Загрузка файлов │
|
||
│ ├── events.py - SSE endpoints ⬅️ ВАЖНО │
|
||
│ └── n8n_proxy.py - Безопасный proxy к n8n ⬅️ НОВОЕ │
|
||
│ │
|
||
│ 🔌 Подключения: │
|
||
│ ├── PostgreSQL (147.45.189.234:5432) - claims, claim_files │
|
||
│ ├── MySQL (localhost:3306) - проверка полисов ERV │
|
||
│ ├── Redis (crm.clientright.ru:6379) - Pub/Sub для SSE │
|
||
│ ├── RabbitMQ (185.197.75.249:5672) - очереди │
|
||
│ └── S3 (Timeweb) - хранилище файлов │
|
||
└────────────┬────────────────────────────────────────────────────────┘
|
||
│
|
||
├──────────────────────────────────────────────────────┐
|
||
│ │
|
||
▼ ▼
|
||
┌──────────────────────────┐ ┌──────────────────────────────┐
|
||
│ N8N Workflows │ │ vTiger CRM │
|
||
│ n8n.clientright.pro │ │ crm.clientright.ru │
|
||
│ │ │ │
|
||
│ Webhook 1: Policy Check │ │ Webservice Operations: │
|
||
│ - MySQL query │ │ - CreateWebContact │
|
||
│ - PostgreSQL insert │ │ - CreateWebProject │
|
||
│ - Return insured_persons│ │ - Query │
|
||
│ │ │ │
|
||
│ Webhook 2: File Upload │ │ Tables: │
|
||
│ - S3 upload │────────────────────│ - vtiger_contactdetails │
|
||
│ - PostgreSQL insert │ n8n webhook │ - vtiger_project │
|
||
│ - OCR Service │ creates contact │ - vtiger_contactscf (mobile) │
|
||
│ - AI Vision (Gemini) │ │ - vtiger_projectcf (cf_1885) │
|
||
│ - Redis PUBLISH │ │ │
|
||
└───────────┬──────────────┘ └───────────────────────────────┘
|
||
│
|
||
│ Redis PUBLISH: ocr_events:{claim_id}
|
||
▼
|
||
┌──────────────────────────────────────────────────────────────────────┐
|
||
│ Redis Pub/Sub │
|
||
│ crm.clientright.ru:6379 │
|
||
│ Channel: ocr_events:{claim_id} │
|
||
│ Password: CRM_Redis_Pass_2025_Secure! │
|
||
└───────────▲──────────────────────────────────────────────────────────┘
|
||
│
|
||
│ SUBSCRIBE
|
||
│
|
||
Backend SSE endpoint (/events/{task_id})
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Ключевые компоненты
|
||
|
||
### Frontend (React + TypeScript)
|
||
|
||
**Основные файлы:**
|
||
- `ClaimForm.tsx` - главный визард с прогрессом
|
||
- `Step1Phone.tsx` - ввод телефона + SMS
|
||
- `Step2Policy.tsx` - проверка полиса
|
||
- `Step3EventType.tsx` - выбор типа события
|
||
- `StepDocumentUpload.tsx` - загрузка документов (динамический)
|
||
- `StepPayment.tsx` - реквизиты для выплаты
|
||
- `utils/pdfConverter.ts` - клиентская конвертация в PDF
|
||
|
||
**Технологии:**
|
||
- Vite (dev server + proxy)
|
||
- Ant Design (UI компоненты)
|
||
- EventSource (SSE client)
|
||
- jsPDF + browser-image-compression
|
||
|
||
### Backend (FastAPI + Python)
|
||
|
||
**Основные модули:**
|
||
- `app/main.py` - FastAPI приложение
|
||
- `app/config.py` - настройки из `.env`
|
||
- `app/api/*` - роутеры (sms, claims, policy, upload, events, n8n_proxy)
|
||
- `app/services/*` - сервисы (database, redis, rabbitmq, s3, policy)
|
||
|
||
**Технологии:**
|
||
- FastAPI (async API)
|
||
- SQLAlchemy (PostgreSQL)
|
||
- aiomysql (MySQL)
|
||
- redis-py (Redis Pub/Sub)
|
||
- aio-pika (RabbitMQ)
|
||
- boto3 (S3)
|
||
- httpx (HTTP client для proxy)
|
||
|
||
### N8N Workflows
|
||
|
||
**Workflow 1: Policy Check**
|
||
```
|
||
Webhook → PostgreSQL (INSERT claims) → MySQL (SELECT policy) → Code → Response
|
||
```
|
||
|
||
**Workflow 2: File Upload + OCR**
|
||
```
|
||
Webhook → S3 Upload → PostgreSQL (INSERT claim_files)
|
||
→ OCR Service → Vision AI → Code (валидация)
|
||
→ PostgreSQL (UPDATE ai_extracted_data) → Redis PUBLISH
|
||
```
|
||
|
||
### Database Schema
|
||
|
||
**PostgreSQL (147.45.189.234:5432/default_db):**
|
||
- `claims` - заявки (claim_number, policy_number, status, form_data::jsonb)
|
||
- `claim_files` - файлы (s3_key, ocr_text, ai_extracted_data::jsonb, ocr_status)
|
||
|
||
**MySQL (localhost:3306/u2768571_crm_db):**
|
||
- `lexrpiority` - полисы ERV
|
||
- `lexrpiority_insured_persons` - застрахованные лица
|
||
|
||
**vTiger CRM (crm.clientright.ru):**
|
||
- `vtiger_contactdetails` - контакты
|
||
- `vtiger_contactscf` - доп.поля контактов (mobile)
|
||
- `vtiger_project` - проекты (заявки)
|
||
- `vtiger_projectcf` - доп.поля проектов (cf_1885 = полис)
|
||
|
||
---
|
||
|
||
## 🔒 Безопасность
|
||
|
||
### N8N Webhooks (спрятаны):
|
||
```
|
||
❌ РАНЬШЕ: fetch('https://n8n.../webhook/9eb7bc5b...') // Виден в коде!
|
||
✅ ТЕПЕРЬ: fetch('/api/n8n/policy/check') // Proxy через backend
|
||
```
|
||
|
||
**Webhook URLs хранятся в:**
|
||
- `.env` (не коммитится в git)
|
||
- `backend/app/config.py` (читает из .env)
|
||
- `backend/app/api/n8n_proxy.py` (проксирует запросы)
|
||
|
||
### Redis:
|
||
```
|
||
Host: crm.clientright.ru:6379
|
||
Password: CRM_Redis_Pass_2025_Secure! (в .env)
|
||
```
|
||
|
||
### PostgreSQL:
|
||
```
|
||
Host: 147.45.189.234:5432
|
||
User: gen_user
|
||
Password: 2~~9_^kVsU?2\S (в .env)
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 Статистика проекта
|
||
|
||
### Документация:
|
||
```
|
||
SESSION_LOG_2025-10-24.md: 708 строк
|
||
SESSION_LOG_2025-10-26.md: 932 строки
|
||
SESSION_LOG_2025-10-28.md: 1063 строки
|
||
SESSION_LOG_2025-10-29.md: 645 строк
|
||
SESSION_LOG_2025-10-29_part2.md: 627 строк
|
||
SESSION_LOG_2025-10-30.md: 597 строк
|
||
SESSION_LOG_2025-11-01.md: 723 строки
|
||
───────────────────────────────────────────
|
||
ИТОГО: 5295 строк
|
||
|
||
Дополнительно:
|
||
- N8N_INTEGRATION.md
|
||
- N8N_SQL_QUERIES.md
|
||
- N8N_PDF_COMPRESS.md
|
||
- SECURITY_N8N_PROXY.md
|
||
```
|
||
|
||
### Git коммиты:
|
||
```
|
||
Всего: 50+ коммитов
|
||
Период: 24 окт - 1 ноя (8 дней)
|
||
Среднее: 6-7 коммитов/день
|
||
```
|
||
|
||
### Файлы проекта:
|
||
```
|
||
Backend: ~30 файлов Python
|
||
Frontend: ~20 файлов TypeScript/TSX
|
||
Configs: ~10 файлов (docker, vite, env)
|
||
Docs: ~10 файлов Markdown
|
||
CRM: ~3 файла PHP (vTiger operations)
|
||
Utils: ~5 скриптов (мониторинг, тесты)
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 Что работает сейчас
|
||
|
||
### ✅ Полный флоу обработки заявки:
|
||
|
||
1. **Step 1: Телефон**
|
||
- Ввод телефона → SMS код → верификация
|
||
- Создание контакта в CRM (автоматически через n8n)
|
||
|
||
2. **Step 2: Полис**
|
||
- Ввод номера полиса
|
||
- Проверка в MySQL БД
|
||
- Если найден → показ застрахованных лиц → переход дальше
|
||
- Если НЕ найден → загрузка скана полиса
|
||
- OCR + Vision AI → распознавание полиса
|
||
- Real-time результат через SSE в модалке
|
||
|
||
3. **Step 3: Тип события**
|
||
- Выбор типа (задержка, отмена, пропуск стыковки)
|
||
- Динамическое определение нужных документов
|
||
|
||
4. **Step 4-N: Документы**
|
||
- Каждый документ = отдельный шаг
|
||
- Загрузка → S3 → OCR → AI → результат в модалке
|
||
- Real-time обработка через SSE
|
||
|
||
5. **Step N: Оплата**
|
||
- Реквизиты для СБП
|
||
- Финальная отправка заявки
|
||
|
||
### ✅ Интеграции:
|
||
|
||
- **n8n** - асинхронная обработка (webhooks)
|
||
- **vTiger CRM** - создание контактов и проектов (webservice)
|
||
- **PostgreSQL** - хранение заявок и файлов
|
||
- **MySQL** - база полисов ERV
|
||
- **Redis** - Pub/Sub для SSE событий
|
||
- **RabbitMQ** - очереди задач
|
||
- **S3 Timeweb** - хранилище файлов
|
||
- **OCR Service** - распознавание текста
|
||
- **Gemini Vision AI** - извлечение данных из документов
|
||
- **SigmaSMS** - отправка SMS кодов
|
||
|
||
---
|
||
|
||
## 📊 Текущий статус (1 ноября 2025)
|
||
|
||
### Git:
|
||
```bash
|
||
Branch: main
|
||
Status: clean (nothing to commit)
|
||
Last commit: c049ed6 - "fix: Добавлены n8n webhook URLs в docker-compose.yml"
|
||
```
|
||
|
||
### Сервисы:
|
||
```bash
|
||
✅ Backend: http://147.45.146.17:8100 (PID 31571, uvicorn --reload)
|
||
✅ Frontend: http://147.45.146.17:5173 (Docker, Vite dev mode)
|
||
✅ PostgreSQL: 147.45.189.234:5432 (внешний)
|
||
✅ Redis: crm.clientright.ru:6379 (внешний)
|
||
✅ RabbitMQ: 185.197.75.249:5672 (внешний)
|
||
✅ MySQL: localhost:3306 (vTiger/ERV полисы)
|
||
✅ S3: s3.twcstorage.ru (Timeweb)
|
||
✅ OCR: 147.45.146.17:8001 (Python service)
|
||
✅ n8n: n8n.clientright.pro (workflows)
|
||
✅ vTiger: crm.clientright.ru (CRM)
|
||
```
|
||
|
||
### Следующие шаги:
|
||
- [ ] Production mode для frontend (сейчас dev)
|
||
- [ ] Docker Compose для backend (сейчас venv на хосте)
|
||
- [ ] Nginx reverse proxy вместо прямого доступа к портам
|
||
- [ ] SSL сертификаты
|
||
- [ ] Мониторинг (Grafana/Prometheus)
|
||
- [ ] Тесты (pytest для backend, Jest для frontend)
|
||
- [ ] CI/CD pipeline
|
||
|
||
---
|
||
|
||
**Последнее обновление:** 1 ноября 2025, 13:39 MSK
|
||
**Автор:** Фёдор + AI Assistant (Claude Sonnet 4.5)
|
||
|
||
|