- Исправлена ошибка ReferenceError при загрузке черновиков - Переименована локальная переменная claimId в finalClaimId для избежания конфликта с параметром функции - Обновлена логика извлечения claim_id из разных источников (claim.claim_id, payload.claim_id, body.claim_id, claim.id) - Добавлен fallback на параметр claimId функции для надёжности
20 KiB
20 KiB
🏗️ ERV Insurance Platform - Архитектура проекта
Дата создания: 24.10.2025
Технологии: Python FastAPI + React TypeScript
Статус: 🚧 В разработке
📊 Обзор архитектуры
Технологический стек:
┌─────────────────────────────────────────────────────────────┐
│ Frontend Layer (React) │
│ ├─ React 18 + TypeScript │
│ ├─ Vite (сборка) │
│ ├─ Ant Design (UI компоненты) │
│ ├─ React Query (API кеш) │
│ ├─ Zustand (state) │
│ └─ WebSocket для real-time │
└─────────────────────────────────────────────────────────────┘
│ REST API + WebSocket
▼
┌─────────────────────────────────────────────────────────────┐
│ Backend Layer (Python FastAPI) │
│ ├─ FastAPI (асинхронный web framework) │
│ ├─ Pydantic (валидация данных) │
│ ├─ SQLAlchemy (PostgreSQL ORM) │
│ ├─ Redis (кеш, rate limiting) │
│ ├─ RabbitMQ (очереди задач) │
│ ├─ Celery (воркеры) │
│ └─ pytest (тесты) │
└─────────────────────────────────────────────────────────────┘
│
├─► PostgreSQL (147.45.189.234) - логи, данные
├─► Redis (localhost:6379) - кеш
├─► RabbitMQ (185.197.75.249) - очереди
├─► MySQL (localhost) - проверка полисов
├─► OCR Service (147.45.146.17:8001)
├─► OpenRouter AI (Gemini Vision)
├─► FlightAware API
└─► S3 Timeweb Cloud
📁 Структура проекта
erv_platform/
│
├─ backend/ ← Python FastAPI
│ ├─ app/
│ │ ├─ main.py ← Точка входа FastAPI
│ │ ├─ config.py ← Настройки из .env
│ │ │
│ │ ├─ api/ ← API endpoints
│ │ │ ├─ __init__.py
│ │ │ ├─ deps.py ← Зависимости (auth, db)
│ │ │ └─ endpoints/
│ │ │ ├─ documents.py ← OCR, Vision
│ │ │ ├─ flights.py ← Проверка рейсов
│ │ │ ├─ claims.py ← Создание обращений
│ │ │ ├─ policies.py ← Проверка полисов
│ │ │ └─ analytics.py ← Статистика
│ │ │
│ │ ├─ services/ ← Бизнес-логика
│ │ │ ├─ ocr_service.py
│ │ │ ├─ ai_service.py ← OpenRouter Vision
│ │ │ ├─ flight_service.py ← FlightAware
│ │ │ ├─ s3_service.py ← Timeweb S3
│ │ │ ├─ crm_service.py ← Интеграция с Vtiger
│ │ │ ├─ cache_service.py ← Redis
│ │ │ └─ queue_service.py ← RabbitMQ
│ │ │
│ │ ├─ models/ ← Pydantic модели
│ │ │ ├─ claim.py ← Обращение
│ │ │ ├─ document.py ← Документ
│ │ │ ├─ passport.py ← Паспорт
│ │ │ ├─ ticket.py ← Билет
│ │ │ └─ flight.py ← Рейс
│ │ │
│ │ ├─ db/ ← База данных
│ │ │ ├─ postgres.py ← PostgreSQL session
│ │ │ ├─ mysql.py ← MySQL session (полисы)
│ │ │ └─ models.py ← SQLAlchemy модели
│ │ │
│ │ ├─ workers/ ← RabbitMQ воркеры
│ │ │ ├─ ocr_worker.py
│ │ │ ├─ flight_worker.py
│ │ │ └─ notification_worker.py
│ │ │
│ │ ├─ core/ ← Утилиты
│ │ │ ├─ security.py ← Rate limiting
│ │ │ ├─ logger.py ← Логирование
│ │ │ └─ exceptions.py
│ │ │
│ │ └─ tests/ ← Тесты
│ │ ├─ test_api.py
│ │ └─ test_services.py
│ │
│ ├─ migrations/ ← SQL миграции
│ │ ├─ 001_create_logs.sql
│ │ ├─ 002_create_documents.sql
│ │ └─ 003_create_claims.sql
│ │
│ ├─ requirements.txt ← Python зависимости
│ ├─ .env.example
│ ├─ Dockerfile
│ └─ pyproject.toml
│
├─ frontend/ ← React TypeScript
│ ├─ src/
│ │ ├─ App.tsx ← Главный компонент
│ │ ├─ main.tsx ← Точка входа
│ │ │
│ │ ├─ components/ ← UI компоненты
│ │ │ ├─ layout/
│ │ │ │ ├─ Header.tsx
│ │ │ │ └─ Footer.tsx
│ │ │ ├─ form/
│ │ │ │ ├─ FormWizard.tsx ← Многошаговая форма
│ │ │ │ ├─ StepPersonal.tsx ← Шаг 1
│ │ │ │ ├─ StepFlight.tsx ← Шаг 2
│ │ │ │ └─ StepDocuments.tsx ← Шаг 3
│ │ │ ├─ upload/
│ │ │ │ ├─ PassportUpload.tsx ← Загрузка паспорта + OCR
│ │ │ │ ├─ TicketUpload.tsx ← Загрузка билета + OCR
│ │ │ │ └─ DocumentUpload.tsx ← Общий компонент
│ │ │ ├─ flight/
│ │ │ │ ├─ FlightChecker.tsx ← Проверка рейса
│ │ │ │ └─ FlightStatus.tsx ← Показ статуса
│ │ │ └─ common/
│ │ │ ├─ Loading.tsx
│ │ │ ├─ ErrorBoundary.tsx
│ │ │ └─ Notification.tsx
│ │ │
│ │ ├─ pages/ ← Страницы
│ │ │ ├─ ClaimForm.tsx ← Главная форма
│ │ │ ├─ Success.tsx ← Успех
│ │ │ └─ Error.tsx ← Ошибка
│ │ │
│ │ ├─ api/ ← API клиент
│ │ │ ├─ client.ts ← Axios instance
│ │ │ ├─ documents.ts ← API документов
│ │ │ ├─ flights.ts ← API рейсов
│ │ │ └─ claims.ts ← API обращений
│ │ │
│ │ ├─ hooks/ ← React hooks
│ │ │ ├─ useOCR.ts
│ │ │ ├─ useFlightCheck.ts
│ │ │ └─ useWebSocket.ts
│ │ │
│ │ ├─ types/ ← TypeScript типы
│ │ │ ├─ claim.ts
│ │ │ ├─ document.ts
│ │ │ └─ flight.ts
│ │ │
│ │ └─ utils/ ← Утилиты
│ │ ├─ validators.ts
│ │ └─ formatters.ts
│ │
│ ├─ public/
│ │ ├─ index.html
│ │ └─ favicon.ico
│ │
│ ├─ package.json
│ ├─ tsconfig.json
│ ├─ vite.config.ts
│ └─ .env.example
│
├─ docker/
│ ├─ Dockerfile.backend
│ ├─ Dockerfile.frontend
│ ├─ docker-compose.dev.yml
│ ├─ docker-compose.prod.yml
│ └─ nginx.conf
│
├─ docs/
│ ├─ API.md ← API документация
│ ├─ DEPLOYMENT.md ← Деплой
│ └─ ARCHITECTURE.md ← Этот файл
│
├─ .gitignore
├─ README.md
└─ docker-compose.yml ← Главный compose
🔄 Поток данных (детально):
1. Загрузка паспорта с OCR:
[React] Пользователь загружает passport.jpg
↓
[React] PassportUpload.tsx
↓ FormData
[FastAPI] POST /api/v1/documents/scan
↓
[FastAPI] DocumentService
├─► S3Service.upload() → s3_url
├─► RabbitMQ.publish('ocr_queue', {s3_url, type: 'passport'})
└─► return {"job_id": "abc-123", "status": "processing"}
↓
[React] Показывает: "⏳ Распознаём паспорт..."
[React] WebSocket подключение: ws://api/ws/jobs/abc-123
↓
[Worker] OCR Worker получает из RabbitMQ
├─► OCRService.analyze(s3_url) → текст (3 сек)
├─► AIService.extract_passport(text) → JSON (2 сек)
├─► PostgreSQL.log(processing_result)
└─► WebSocket.send_to_client(session_id, result)
↓
[React] WebSocket получает результат
↓
[React] Автозаполняет поля:
form.setFieldsValue({
lastname: "Иванов",
firstname: "Иван",
...
})
↓
[React] Показывает: "✅ Паспорт распознан! Проверьте данные."
Время: ~5 секунд, но пользователь видит прогресс!
2. Проверка рейса:
[React] Пользователь ввёл "SU1234" и дату
↓
[React] FlightChecker.tsx
↓ debounce 500ms (не дёргаем API на каждый символ)
[FastAPI] GET /api/v1/flights/check?number=SU1234&date=2025-10-24
↓
[FastAPI] FlightService
├─► Redis.get('flight:SU1234:2025-10-24')
│ └─► Если есть → возврат из кеша (0.001 сек)
│
└─► Если нет:
├─► FlightAwareAPI.check() (1-2 сек)
├─► Redis.set('flight:...', result, ttl=3600) ← Кеш на час
└─► return result
↓
[React] Показывает красивую карточку:
┌─────────────────────────────────────┐
│ ✈️ Рейс SU1234 │
│ Moscow (SVO) → Ufa (UFA) │
│ Статус: ⚠️ Задержка 45 минут │
│ Вылет: 09:25 (план: 08:40) │
└─────────────────────────────────────┘
3. Отправка обращения:
[React] Пользователь заполнил всё, жмёт "Подать обращение"
↓
[FastAPI] POST /api/v1/claims/submit
{
client: {lastname, firstname, ...},
flight: {number, date, type: "delay_flight"},
documents: [s3_urls],
sbp_phone: "+79991234567"
}
↓
[FastAPI] ClaimService
├─► Валидация (Pydantic)
├─► PostgreSQL: сохранение claim (для аналитики)
├─► Формирование payload для CRM
│ {
│ "client": {...},
│ "contractor": {...}, ← Из конфига
│ "project": {...},
│ "ticket": {...}
│ }
├─► POST → https://form.clientright.ru/server_webservice2.php
│ └─► PHP Bridge → Vtiger Webservices (PHP 7.3)
│ └─► CRM создаёт тикет
│
├─► RabbitMQ.publish('notifications', {...}) ← Email в фоне
└─► return {"success": true, "ticket_id": "TT12345"}
↓
[React] Редирект на Success.tsx
"✅ Ваше обращение #TT12345 принято!"
🔌 Интеграции (детально):
OCR + Vision Pipeline:
# services/document_processor.py
async def process_passport(file_url: str) -> PassportData:
# 1. OCR (ваш сервис)
ocr_response = await ocr_service.analyze(
file_url=file_url,
file_type="auto" # Автоопределение
)
ocr_text = ocr_response['pages_data'][0]['ocr_text']
# 2. Vision AI (OpenRouter Gemini)
vision_prompt = """
Извлеки из текста паспорта РФ следующие данные в формате JSON:
{
"surname": "...",
"name": "...",
"patronymic": "...",
"birthdate": "DD.MM.YYYY",
"passport_series": "XXXX",
"passport_number": "XXXXXX"
}
Текст паспорта:
{ocr_text}
"""
vision_result = await ai_service.extract_structured_data(
prompt=vision_prompt.format(ocr_text=ocr_text)
)
# 3. Валидация через Pydantic
passport = PassportData(**vision_result)
# 4. Логирование в PostgreSQL
await db.execute("""
INSERT INTO document_processing
(file_url, document_type, ocr_text, vision_result, processing_time_ms)
VALUES ($1, $2, $3, $4, $5)
""", file_url, 'passport', ocr_text, vision_result, elapsed_ms)
return passport
📡 API Спецификация:
Базовый URL:
DEV: http://localhost:8000/api/v1
PROD: https://api.erv-claims.clientright.ru/api/v1
Endpoints:
| Method | Endpoint | Описание | Время |
|---|---|---|---|
| POST | /documents/upload |
Загрузка в S3 | ~500ms |
| POST | /documents/scan |
OCR + Vision | ~5s (async) |
| GET | /flights/check |
Проверка рейса | ~200ms (cache) |
| GET | /policies/verify |
Проверка полиса | ~5ms (cache) |
| POST | /claims/submit |
Создание обращения | ~1s |
| WS | /ws/jobs/{job_id} |
Real-time статусы | - |
Пример: Scan Passport
Request:
POST /api/v1/documents/scan
Content-Type: application/json
{
"file_url": "https://s3.twcstorage.ru/.../passport.jpg",
"document_type": "passport"
}
Response (сразу):
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"estimated_time_seconds": 5
}
WebSocket update (через 5 сек):
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"data": {
"surname": "Иванов",
"name": "Иван",
"patronymic": "Иванович",
"birthdate": "01.01.1990",
"passport_series": "4510",
"passport_number": "123456"
}
}
🗄️ Структура баз данных:
PostgreSQL (новые данные):
-- Обращения (дубликат для аналитики)
CREATE TABLE claims (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id VARCHAR(100),
insurance_type VARCHAR(50), -- erv_flight, erv_train, etc.
-- Данные клиента (JSONB)
client_data JSONB,
-- Данные события (JSONB)
event_data JSONB,
-- Документы
documents JSONB, -- [{type, s3_url, ocr_result, vision_result}]
-- Статус
status VARCHAR(20), -- processing, submitted, completed, error
crm_ticket_id VARCHAR(50),
-- Метаданные
ip_address INET,
user_agent TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_claims_status ON claims(status);
CREATE INDEX idx_claims_created ON claims(created_at);
CREATE INDEX idx_claims_crm_ticket ON claims(crm_ticket_id);
-- Обработка документов
CREATE TABLE document_processing (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
claim_id UUID REFERENCES claims(id),
file_url TEXT,
s3_url TEXT,
document_type VARCHAR(50), -- passport, ticket, etc.
-- OCR результат
ocr_text TEXT,
ocr_confidence NUMERIC,
-- Vision результат
vision_result JSONB,
-- Метрики
processing_time_ms INTEGER,
ocr_time_ms INTEGER,
vision_time_ms INTEGER,
created_at TIMESTAMP DEFAULT NOW()
);
-- Логи
CREATE TABLE logs (
id BIGSERIAL PRIMARY KEY,
level VARCHAR(10), -- INFO, WARNING, ERROR
logger VARCHAR(50), -- ocr_service, flight_service, etc.
message TEXT,
context JSONB,
session_id VARCHAR(100),
ip_address INET,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_logs_level ON logs(level);
CREATE INDEX idx_logs_created ON logs(created_at DESC);
CREATE INDEX idx_logs_session ON logs(session_id);
-- Кеш API (fallback)
CREATE TABLE api_cache (
cache_key VARCHAR(255) PRIMARY KEY,
cache_value JSONB,
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
MySQL (существующая CRM база):
-- НЕ трогаем! Только читаем
ci20465_erv.lexrpiority
├─ voucher
├─ insured_from
└─ insured_to
🚀 Deployment стратегия:
Development (сейчас):
# 1. Backend
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
# 2. Frontend
cd frontend
npm install
npm run dev # Vite dev server на :5173
# 3. Доступ:
Frontend: http://localhost:5173
Backend: http://localhost:8000
API Docs: http://localhost:8000/docs ← Swagger UI!
Production (потом):
# Вариант 1: Тот же сервер, другой домен
cd /var/www/erv-claims.clientright.ru/
git clone http://147.45.146.17:3002/fedya/erv-platform.git .
docker-compose -f docker/docker-compose.prod.yml up -d
# Nginx конфиг:
server {
server_name erv-claims.clientright.ru;
# Frontend (статика)
location / {
root /var/www/erv-claims/frontend/dist;
try_files $uri $uri/ /index.html;
}
# Backend API
location /api/ {
proxy_pass http://localhost:8000;
}
# WebSocket
location /ws/ {
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
🎯 Что делаю СЕЙЧАС (следующие 2-3 часа):
Создаю базу:
- ✅ Структура проекта (сделано)
- ✅ requirements.txt (Python зависимости)
- ✅ package.json (React зависимости)
- ✅ .env.example
- ✅ Базовый FastAPI app
- ✅ Базовый React app
- ✅ SQL миграции для PostgreSQL
- ✅ Docker compose для dev
- ✅ README с инструкциями
Время: ~1 час на базовую настройку
💪 Готов?
Сейчас создам всю базовую структуру и запущу оба приложения (FastAPI + React).
Начинаю прямо сейчас! 🚀