Files
aiform_dev/N8N_SQL_QUERIES.md

435 lines
9.8 KiB
Markdown
Raw Permalink Normal View History

feat: Интеграция n8n + Redis Pub/Sub + SSE для real-time обработки заявок 🎯 Основные изменения: Backend: - ✅ Добавлен SSE endpoint для real-time событий (/api/v1/events/{task_id}) - ✅ Redis Pub/Sub для публикации/подписки на события OCR/Vision - ✅ Удален aioboto3 из requirements.txt (конфликт зависимостей) - ✅ Добавлен OCR worker (deprecated, логика перенесена в n8n) Frontend (React): - ✅ Автогенерация claim_id и session_id - ✅ Клиентская конвертация файлов в PDF (JPG/PNG/HEIC/WEBP) - ✅ Сжатие изображений до 2MB перед конвертацией - ✅ SSE подписка на события OCR/Vision в Step1Policy - ✅ Валидация документов (полис vs неподходящий контент) - ✅ Real-time прогресс загрузки и обработки файлов - ✅ Интеграция с n8n webhooks для проверки полиса и загрузки файлов n8n Workflows: - ✅ Проверка полиса в MySQL + запись в PostgreSQL - ✅ Загрузка файлов в S3 + OCR + Vision AI - ✅ Публикация событий в Redis через backend API - ✅ Валидация документов (распознавание полисов ERV) Документация: - 📝 N8N_INTEGRATION.md - интеграция с n8n - 📝 N8N_SQL_QUERIES.md - SQL запросы для workflows - 📝 N8N_PDF_COMPRESS.md - сжатие PDF - 📝 N8N_STIRLING_COMPRESS.md - интеграция Stirling-PDF Утилиты: - 🔧 monitor_redis.py/sh - мониторинг Redis Pub/Sub - 🔧 test_redis_events.sh - тестирование событий - 🔧 pdfConverter.ts - клиентская конвертация в PDF Архитектура: React → n8n webhooks (sync) → MySQL/PostgreSQL/S3 → n8n workflows (async) → OCR/Vision → Redis Pub/Sub → SSE → React
2025-10-27 08:33:16 +03:00
# 📝 SQL запросы для n8n вебхуков
## PostgreSQL Connection:
- **Host:** `147.45.189.234`
- **Port:** `5432`
- **Database:** `default_db`
- **User:** `gen_user`
- **Password:** `2~~9_^kVsU?2\S`
---
## 1⃣ Создание заявки (при генерации claim_id)
**Вебхук:** `POST /webhook/create-claim`
**Input:**
```json
{
"claim_id": "CLM-2025-10-25-A3F7G2",
"voucher": "E1000-302372730",
"client_phone": "",
"client_email": "",
"session_id": "sess-abc-123"
}
```
**SQL (PostgreSQL Node):**
```sql
INSERT INTO claims (
claim_number,
policy_number,
client_phone,
client_email,
status,
insurance_type,
source,
form_data,
created_at
) VALUES (
'{{ $json.body.claim_id }}',
'{{ $json.body.voucher }}',
'{{ $json.body.client_phone || "" }}',
'{{ $json.body.client_email || "" }}',
'draft',
'erv_travel',
'web_form',
'{{ JSON.stringify($json.body) }}',
NOW()
)
ON CONFLICT (claim_number) DO UPDATE SET
updated_at = NOW(),
form_data = EXCLUDED.form_data
RETURNING id, claim_number, created_at;
```
**Response:**
```json
{
"success": true,
"claim_id": "CLM-2025-10-25-A3F7G2",
"db_id": "uuid-from-db",
"created_at": "2025-10-25T10:00:00Z"
}
```
---
## 2⃣ Сохранение файла в claim_files
**После S3 Upload в том же workflow!**
**SQL (PostgreSQL Node после S3):**
```sql
INSERT INTO claim_files (
claim_id,
file_name,
file_path,
file_size,
mime_type,
file_type,
s3_bucket,
s3_key,
s3_url,
ocr_status,
created_at
)
SELECT
c.id,
'{{ $json.file.original_name }}',
'{{ $json.s3.key }}',
{{ $json.file.size || 0 }},
'{{ $json.file.mime_type }}',
'{{ $json.claim.file_type }}',
'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c',
'{{ $json.s3.key }}',
'{{ $('Upload a file1').item.json.Location }}',
'pending',
NOW()
FROM claims c
WHERE c.claim_number = '{{ $json.claim.claim_id }}'
RETURNING id as file_id, s3_url, ocr_status;
```
**Response (добавь в Respond):**
```json
{
"success": true,
"claim_id": "CLM-2025-10-25-A3F7G2",
"file": {
"file_id": "uuid-from-db",
"type": "policy_scan",
"url": "https://s3.../policy_scan.pdf",
"s3_key": "files/erv/ticket/CLM-xxx/policy_scan.pdf",
"ocr_status": "pending"
}
}
```
---
## 3⃣ Обновление OCR результата
**OCR Workflow (после обработки):**
**SQL:**
```sql
UPDATE claim_files
SET
ocr_status = 'completed',
ocr_text = '{{ $json.ocr_text }}',
processed_at = NOW()
WHERE id = '{{ $json.file_id }}'
RETURNING id, ocr_status;
```
---
## 4⃣ Обновление Vision AI результата
**SQL:**
```sql
UPDATE claim_files
SET
ai_extracted_data = '{{ JSON.stringify($json.ai_analysis) }}',
processed_at = NOW()
WHERE id = '{{ $json.file_id }}'
RETURNING id, ai_extracted_data;
```
**Пример ai_extracted_data:**
```json
{
"document_type": "policy",
"is_valid": true,
"confidence": 0.95,
"voucher": "E1000-302372730",
"holder_name": "IVANOV IVAN",
"insured_from": "01.11.2025",
"insured_to": "30.11.2025"
}
```
---
## 5⃣ Получить все файлы заявки
**Вебхук:** `GET /webhook/get-claim-files/{claim_id}`
**SQL:**
```sql
SELECT
cf.id,
cf.file_name,
cf.file_type,
cf.s3_url,
cf.file_size,
cf.ocr_status,
cf.ocr_text,
cf.ai_extracted_data,
cf.created_at,
cf.processed_at
FROM claim_files cf
JOIN claims c ON c.id = cf.claim_id
WHERE c.claim_number = '{{ $parameter.claim_id }}'
ORDER BY cf.created_at;
```
**Response:**
```json
{
"success": true,
"claim_id": "CLM-2025-10-25-A3F7G2",
"files": [
{
"file_id": "...",
"file_type": "policy_scan",
"s3_url": "...",
"ocr_status": "completed",
"ocr_text": "ЕВРОИНС...",
"ai_extracted_data": {...}
}
]
}
```
---
## 6⃣ Финальная отправка заявки
**SQL (обновляем статус):**
```sql
UPDATE claims
SET
status = 'submitted',
client_phone = '{{ $json.phone }}',
client_email = '{{ $json.email }}',
form_data = '{{ JSON.stringify($json.form_data) }}',
submitted_at = NOW(),
updated_at = NOW()
WHERE claim_number = '{{ $json.claim_id }}'
RETURNING id, claim_number, status, submitted_at;
```
---
## 7⃣ Публикация результатов OCR/Vision в Redis
**После OCR/Vision обработки - отправляем результат в React через Redis Pub/Sub**
### Webhook для публикации:
**POST** `http://147.45.189.234:8000/events/{claim_id}`
**Headers:**
```
Content-Type: application/json
```
**Body (n8n Code Node):**
```json
{
"event_type": "ocr_completed",
"status": "success",
"data": {
"file_id": "{{ $json.file_id }}",
"file_type": "policy_scan",
"is_valid_document": true,
"document_type": "ERV Travel Insurance Policy",
"ocr_text": "E1000-302372730",
"confidence": 0.95,
"ai_analysis": {
"is_policy": true,
"contains_policy_number": true,
"is_nsfw": false,
"warnings": []
}
},
"message": "✅ Распознан полис страхования ERV",
"timestamp": "{{ new Date().toISOString() }}"
}
```
---
### Code Node для валидации документа:
**После OCR + Vision:**
```javascript
// Получаем результаты OCR и Vision
const ocrData = $json.ocr_result; // Из предыдущей ноды
const visionData = $json.vision_result;
// Валидация документа
const validation = {
is_valid_document: false,
document_type: 'unknown',
confidence: 0,
warnings: []
};
// 1. Проверка на NSFW
if (visionData.nsfw === true || visionData.nsfw_score > 0.7) {
validation.warnings.push('Неподходящее содержимое изображения');
validation.is_valid_document = false;
validation.document_type = 'inappropriate_content';
}
// 2. Проверка текста OCR на наличие номера полиса
const policyNumberRegex = /[A-Z]\d{4}-\d{9}/;
const hasPolicyNumber = policyNumberRegex.test(ocrData.ocr_text);
if (hasPolicyNumber) {
validation.is_valid_document = true;
validation.document_type = 'ERV Travel Insurance Policy';
validation.confidence = 0.9;
} else {
validation.warnings.push('Номер полиса не найден');
}
// 3. Анализ Vision описания
const visionText = visionData.content?.toLowerCase() || '';
const insuranceKeywords = ['страхов', 'insurance', 'полис', 'policy', 'erv'];
const hasInsuranceKeywords = insuranceKeywords.some(kw => visionText.includes(kw));
if (hasInsuranceKeywords) {
validation.confidence += 0.05;
} else {
validation.warnings.push('Документ не похож на страховой полис');
validation.is_valid_document = false;
}
// 4. Формируем результат для публикации в Redis
const result = {
file_id: $json.file_id,
claim_id: $json.claim_id,
event_type: 'ocr_completed',
status: validation.is_valid_document ? 'success' : 'error',
data: {
file_id: $json.file_id,
file_type: $json.file_type,
is_valid_document: validation.is_valid_document,
document_type: validation.document_type,
ocr_text: ocrData.ocr_text,
confidence: validation.confidence,
ai_analysis: {
is_policy: validation.is_valid_document,
contains_policy_number: hasPolicyNumber,
is_nsfw: visionData.nsfw,
nsfw_score: visionData.nsfw_score,
warnings: validation.warnings
}
},
message: validation.is_valid_document
? '✅ Распознан полис страхования ERV'
: `❌ ${validation.warnings.join(', ')}`,
timestamp: new Date().toISOString()
};
return result;
```
---
### HTTP Request Node (публикация в Redis):
**Method:** `POST`
**URL:** `http://147.45.189.234:8000/events/{{ $json.claim_id }}`
**Headers:**
```json
{
"Content-Type": "application/json"
}
```
**Body:**
```json
{{ $json }}
```
---
### React подписка на события:
**Frontend код:**
```typescript
useEffect(() => {
if (!claimId) return;
// Подключаемся к SSE
const eventSource = new EventSource(
`http://147.45.189.234:8000/events/${claimId}`
);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event_type === 'ocr_completed') {
setUploadProgress(''); // Убираем крутилку
if (data.status === 'success' && data.data.is_valid_document) {
message.success(data.message);
// ✅ Полис распознан - можно продолжать
} else {
message.error(data.message);
// ❌ Это не полис - показываем предупреждение
Modal.error({
title: 'Документ не распознан',
content: data.data.ai_analysis.warnings.join('\n')
});
}
}
};
return () => eventSource.close();
}, [claimId]);
```
---
### Полный workflow в n8n:
```
Webhook (file upload)
S3 Upload
PostgreSQL (INSERT claim_files)
OCR Service (HTTP Request)
Vision Service (HTTP Request)
Code Node (валидация документа)
IF Node: is_valid_document?
├─ TRUE → PostgreSQL UPDATE (ocr_status = 'valid')
│ ↓
│ HTTP POST → /events/{claim_id} (Redis Pub/Sub)
│ ↓
│ Respond to Webhook: {success: true}
└─ FALSE → PostgreSQL UPDATE (ocr_status = 'invalid')
HTTP POST → /events/{claim_id} (Redis Pub/Sub)
Respond to Webhook: {success: true, warning: true}
```
---
**Готово! Теперь делаем вебхуки в n8n?** 🚀