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

9.8 KiB
Raw Permalink Blame History

📝 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:

{
  "claim_id": "CLM-2025-10-25-A3F7G2",
  "voucher": "E1000-302372730",
  "client_phone": "",
  "client_email": "",
  "session_id": "sess-abc-123"
}

SQL (PostgreSQL Node):

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:

{
  "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):

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):

{
  "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:

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:

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:

{
  "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:

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:

{
  "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 (обновляем статус):

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):

{
  "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:

// Получаем результаты 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:

{
  "Content-Type": "application/json"
}

Body:

{{ $json }}

React подписка на события:

Frontend код:

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? 🚀