Files
crm.clientright.ru/ticket_form/N8N_SQL_QUERIES.md
Fedor de011efba9 fix: исправлен конфликт имён переменных в loadDraft (claimId -> finalClaimId)
- Исправлена ошибка ReferenceError при загрузке черновиков
- Переименована локальная переменная claimId в finalClaimId для избежания конфликта с параметром функции
- Обновлена логика извлечения claim_id из разных источников (claim.claim_id, payload.claim_id, body.claim_id, claim.id)
- Добавлен fallback на параметр claimId функции для надёжности
2025-11-19 23:33:52 +03:00

435 lines
9.8 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 📝 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?** 🚀