# 📋 Лог сессии: Исправление SSE error handling **Дата:** 28 октября 2025 (00:00 - 01:00 MSK) **Задача:** Исправление ошибки "Ошибка подключения к серверу" при успешном распознавании полиса **Статус:** ✅ Успешно завершено --- ## 🎯 Проблема После успешного распознавания полиса через OCR/Vision, пользователь видел модальное окно с ошибкой: ``` ❌ Ошибка распознавания Ошибка подключения к серверу Полный ответ: null ``` Хотя в логах backend видно, что: - ✅ SSE подключение установлено - ✅ Событие OCR получено из Redis - ✅ Данные отправлены клиенту - ✅ SSE соединение закрыто корректно --- ## 🔍 Диагностика ### Backend логи показывали успешную работу: ``` 2025-10-28 00:41:15,187 - 🚀 SSE connection requested for task_id: CLM-2025-10-27-Y1KWA1 2025-10-28 00:41:15,202 - 📡 Client subscribed to ocr_events:CLM-2025-10-27-Y1KWA1 2025-10-28 00:41:15,203 - ⏳ Waiting for message on ocr_events:CLM-2025-10-27-Y1KWA1... 2025-10-28 00:41:49,729 - 📥 Received message type: message 2025-10-28 00:41:49,729 - 📦 Raw event data: {"claim_id":"CLM-2025-10-27-Y1KWA1","event":{"event_type":"ocr_completed","status":"completed","message":"OCR обработка завершена","data":{"output":{"is_policy":"yes","policy_number":"E1000-302545808"... 2025-10-28 00:41:49,730 - 📦 Unwrapped n8n Redis format for CLM-2025-10-27-Y1KWA1 2025-10-28 00:41:49,730 - 📤 Sending event to client: completed 2025-10-28 00:41:49,730 - ✅ Task CLM-2025-10-27-Y1KWA1 finished, closing SSE ``` **Вывод:** Backend работал корректно! ### Причина ошибки: 1. Backend отправляет событие OCR клиенту 2. Backend **закрывает SSE соединение** (это нормально) 3. Браузер получает событие закрытия SSE 4. Браузер триггерит `eventSource.onerror` 5. Frontend в `onerror` **перезаписывает успешный результат** ошибкой: ```typescript // ❌ СТАРЫЙ КОД (неправильный) eventSource.onerror = (error) => { console.error('❌ SSE connection error:', error); setOcrModalContent({ success: false, data: null, message: 'Ошибка подключения к серверу' }); setWaitingForOcr(false); eventSource.close(); }; ``` **Проблема:** `onerror` вызывается **после** получения результата, когда backend закрывает SSE, и затирает успешный результат. --- ## 🛠️ Решение Добавил проверку в `eventSource.onerror` — если уже получили результат OCR, не затираем его сообщением об ошибке: ```typescript // ✅ НОВЫЙ КОД (правильный) eventSource.onerror = (error) => { console.error('❌ SSE connection error:', error); console.error('SSE readyState:', eventSource.readyState); // Не показываем ошибку если уже получили результат (backend закрыл SSE после успешной отправки) setOcrModalContent((prev) => { if (prev && prev !== 'loading') { console.log('✅ SSE закрыто после получения результата, не показываем ошибку'); return prev; // Оставляем текущий результат } return { success: false, data: null, message: 'Ошибка подключения к серверу' }; }); setWaitingForOcr(false); eventSource.close(); }; ``` **Логика:** - Если `prev !== 'loading'` → значит уже получили результат → **не затираем** его - Если `prev === 'loading'` → реальная ошибка подключения → показываем ошибку --- ## 📝 Изменённые файлы ### `/frontend/src/components/form/Step1Policy.tsx` **Изменение:** Обработка `eventSource.onerror` с проверкой наличия результата **Строки:** 147-162 **Было:** ```typescript eventSource.onerror = (error) => { console.error('❌ SSE connection error:', error); setOcrModalContent({ success: false, data: null, message: 'Ошибка подключения к серверу' }); setWaitingForOcr(false); eventSource.close(); }; ``` **Стало:** ```typescript eventSource.onerror = (error) => { console.error('❌ SSE connection error:', error); console.error('SSE readyState:', eventSource.readyState); setOcrModalContent((prev) => { if (prev && prev !== 'loading') { console.log('✅ SSE закрыто после получения результата, не показываем ошибку'); return prev; } return { success: false, data: null, message: 'Ошибка подключения к серверу' }; }); setWaitingForOcr(false); eventSource.close(); }; ``` --- ## 🐛 Проблемы в процессе исправления ### Проблема 1: Backend завис после kill -HUP **Симптом:** ```bash ERROR: [Errno 98] Address already in use ``` **Причина:** `kill -HUP` не перезапустил uvicorn корректно, порт 8100 остался занят зависшим процессом. **Решение:** ```bash # Убили все процессы на порту 8100 sudo lsof -ti :8100 | xargs -r kill -9 # Перезапустили backend cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform/backend source venv/bin/activate python -m uvicorn app.main:app --host 0.0.0.0 --port 8100 --reload > ../backend.log 2>&1 & ``` ### Проблема 2: Изменения не применились во frontend **Симптом:** После `docker-compose restart frontend` старый код всё ещё работал. **Причина:** Frontend работает в Docker без volume mount — код встроен в образ при сборке. **Решение:** ```bash # Пересборка образа с новым кодом docker-compose build frontend # Пересоздание контейнера docker-compose up -d frontend ``` **Проверка применения изменений:** ```bash docker exec erv_platform_frontend_1 grep -A8 "eventSource.onerror" /app/src/components/form/Step1Policy.tsx ``` --- ## 🚀 Git Commit **Commit:** `0b75e01` **Message:** "fix: Не затираем результат OCR при закрытии SSE соединения" **Полное описание:** ``` Проблема: Backend закрывает SSE после отправки события, браузер триггерит onerror, фронтенд перезаписывал успешный результат сообщением 'Ошибка подключения к серверу'. Решение: Проверяем в onerror что если уже получили результат (prev !== 'loading'), не затираем его ошибкой. ``` **Push:** ✅ `origin/main` --- ## ✅ Результат ### Что работает: 1. ✅ Backend запущен (PID 25931) на порту 8100 2. ✅ Frontend пересобран и работает на http://147.45.146.17:5173 3. ✅ SSE подключение устанавливается корректно 4. ✅ События OCR получаются из Redis через backend 5. ✅ Результат распознавания отображается в модальном окне 6. ✅ **Ошибка "Ошибка подключения к серверу" больше не появляется** 7. ✅ Git репозиторий синхронизирован ### Тестирование: **Сценарий 1: Успешное распознавание полиса** - Загрузка файла полиса → ✅ - SSE подключение → ✅ - OCR/Vision обработка → ✅ - Отображение результата → ✅ "Полис распознан: E1000-302545808" - **Нет ошибки** при закрытии SSE → ✅ **Сценарий 2: Загрузка неподходящего документа** - Загрузка не-полиса → ✅ - SSE подключение → ✅ - OCR/Vision обработка → ✅ - Отображение: "Документ не является полисом ERV" → ✅ **Сценарий 3: Реальная ошибка подключения** - Если backend недоступен → ❌ "Ошибка подключения к серверу" (корректная ошибка) --- ## 📊 Архитектура (финальная) ``` ┌─────────────────────────────────────────────────────────────────────┐ │ USER BROWSER │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ React Frontend (Vite Dev Server, port 3000) │ │ │ │ - Step1Policy.tsx (SSE Client) │ │ │ │ - Модалка с результатом OCR │ │ │ │ - EventSource(`/events/${claimId}`) │ │ │ │ - ✅ Защита от затирания результата в onerror │ │ │ └────────────┬─────────────────────────────────────────────────┘ │ │ │ Vite Proxy (/events → host:8100) │ └───────────────┼─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ BACKEND (FastAPI, port 8100) │ │ PID: 25931 │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ SSE Endpoint: GET /events/{task_id} │ │ │ │ - Подписка на Redis: ocr_events:{task_id} │ │ │ │ - Стриминг событий через SSE │ │ │ │ - Закрытие SSE после отправки результата │ │ │ └────────────┬─────────────────────────────────────────────────┘ │ └───────────────┼──────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Redis Pub/Sub (crm.clientright.ru:6379) │ │ │ │ Channel: ocr_events:CLM-2025-10-27-XXXXX │ │ Format: { │ │ "claim_id": "CLM-...", │ │ "event": { │ │ "event_type": "ocr_completed", │ │ "status": "completed", │ │ "data": { "output": { "is_policy": "yes", ... } } │ │ } │ │ } │ └────────────────▲────────────────────────────────────────────────────┘ │ │ PUBLISH │ ┌────────────────┴────────────────────────────────────────────────────┐ │ n8n Workflow (OCR Processing) │ │ │ │ 1. Webhook trigger (file upload) │ │ 2. Upload to S3 │ │ 3. OCR Service (147.45.146.17:8001) │ │ 4. AI Vision (OpenRouter Gemini 2.0 Flash) │ │ 5. Redis Publish Node → ocr_events:{claim_id} │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 📈 Метрики **Время выполнения сессии:** ~1 час **Количество коммитов:** 1 **Изменённых файлов:** 1 **Строк изменено:** +10 / -1 **Перезапусков backend:** 2 **Rebuild frontend:** 1 **Проблемы решены:** - ✅ Затирание результата OCR при закрытии SSE - ✅ Backend завис после kill -HUP - ✅ Изменения не применялись без rebuild --- ## 🔗 Ссылки - Frontend: http://147.45.146.17:5173 - Backend API: http://localhost:8100 - Backend Health: http://localhost:8100/health - Gitea: http://147.45.146.17:3002/negodiy/erv-platform - n8n: http://147.45.146.17:5678 --- ## 📝 Важные заметки ### Backend запущен вне Docker: ```bash # Процесс PID: 25931 Command: python -m uvicorn app.main:app --host 0.0.0.0 --port 8100 --reload # Логи tail -f /var/www/fastuser/data/www/crm.clientright.ru/erv_platform/backend.log # Перезапуск cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform/backend source venv/bin/activate python -m uvicorn app.main:app --host 0.0.0.0 --port 8100 --reload > ../backend.log 2>&1 & ``` ### Frontend требует rebuild при изменениях: ```bash # Применение изменений docker-compose build frontend docker-compose up -d frontend # Проверка кода в контейнере docker exec erv_platform_frontend_1 cat /app/src/components/form/Step1Policy.tsx ``` ### Redis credentials: ``` Host: crm.clientright.ru Port: 6379 Password: cKSq8M11ZQIRi59OuUXb Channels: ocr_events:{claim_id} ``` --- **Статус:** ✅ Успешно завершено **Автор:** AI Assistant (Claude Sonnet 4.5) **Дата:** 28 октября 2025, 01:00 MSK --- --- # 📋 Лог сессии: Умная форма Step 2 с AI-обработкой документов **Дата:** 28 октября 2025 (13:00 - 17:00 MSK) **Задача:** Рефакторинг Step 2 в интеллектуальную форму с пошаговой загрузкой и AI-обработкой документов **Статус:** ✅ Успешно завершено --- ## 🎯 Основные задачи ### 1. ✅ Улучшение UX на Step 1 (Policy) - Добавлены динамические кнопки в модалке OCR: - **"Продолжить →"** при успешном распознавании → переход на Step 2 - **"Загрузить другой файл"** при ошибке → очистка и повтор - Добавлен **DEV MODE** панель с кнопкой быстрого перехода на Step 2 без валидации ### 2. ✅ Рефакторинг Step 2 (Details) **Было:** - Ручной ввод всех полей (тип события, дата, номер рейса/поезда/парома) - Загрузка документов как дополнение к ручному вводу **Стало:** - **"Интеллектуальная форма"** — AI извлекает данные из документов - **Пошаговая загрузка** каждого документа с индивидуальной обработкой - **Модалка обработки** для каждого документа с результатами извлечения - Ручной ввод только при необходимости (fallback) ### 3. ✅ Определение требований к документам #### Задержка рейса (`delay_flight`) 1. **Посадочный талон ИЛИ Билет** (обязательно) - `file_type: flight_delay_boarding_or_ticket` - `event_type: flight_delay_boarding_or_ticket_processed` - AI извлекает: номер рейса, дату, маршрут, ФИО, время вылета 2. **Подтверждение задержки** (обязательно, до 3 файлов) - `file_type: flight_delay_confirmation` - `event_type: flight_delay_confirmation_processed` - Справка от АК, email/SMS, ИЛИ фото табло - AI извлекает: время задержки, причину, фактическое время вылета #### Отмена рейса (`cancel_flight`) 1. **Билет** (обязательно) - `file_type: flight_cancel_ticket` - `event_type: flight_cancel_ticket_processed` 2. **Уведомление об отмене** (обязательно, до 3 файлов) - `file_type: flight_cancel_notice` - `event_type: flight_cancel_notice_processed` - Письмо/SMS от АК, фото табло #### Пропуск стыковки (`missed_connection`) 1. **Рейс отправления: Посадочный талон ИЛИ Билет** (обязательно) - `file_type: missed_connection_first_boarding_or_ticket` - `event_type: missed_connection_first_boarding_or_ticket_processed` 2. **Рейс прибытия: Билет на пропущенный рейс** (обязательно) - `file_type: missed_connection_second_ticket` - `event_type: missed_connection_second_ticket_processed` 3. **Подтверждение задержки первого рейса** (опционально, до 3 файлов) - `file_type: missed_connection_delay_proof` - `event_type: missed_connection_delay_proof_processed` #### Задержка/отмена поезда (`delay_train`, `cancel_train`) 1. **Билет** (обязательно) - `file_type: train_delay_ticket` / `train_cancel_ticket` 2. **Справка о задержке/отмене** (обязательно, до 3 файлов) - `file_type: train_delay_certificate` / `train_cancel_certificate` - Справка от ЖД, фото табло #### Задержка/отмена парома/круиза (`delay_ferry`, `cancel_ferry`) 1. **Билет/Бронь** (обязательно) - `file_type: ferry_delay_ticket` / `ferry_cancel_ticket` 2. **Подтверждение задержки/отмены** (обязательно, до 3 файлов) - `file_type: ferry_delay_confirmation` / `ferry_cancel_confirmation` - Справка, email, фото расписания ### 4. ✅ Уникальные `file_type` для каждого документа **Принцип:** Каждый тип документа → уникальный `file_type` → уникальный `event_type` в Redis ```typescript // Пример для отмены рейса { file_type: "flight_cancel_ticket" // → S3, n8n, DB event_type: "flight_cancel_ticket_processed" // → Redis pub/sub } { file_type: "flight_cancel_notice" event_type: "flight_cancel_notice_processed" } ``` **Почему это важно:** - n8n разделяет потоки обработки по `file_type` - Разные AI промпты для каждого типа документа - Frontend слушает уникальный `event_type` для каждого документа ### 5. ✅ Добавлены DEV MODE кнопки во все 3 шага **Step 1 (Policy):** - "Далее → (Step 2) [пропустить]" — авто-заполнение voucher и claim_id **Step 2 (Details):** - "← Назад (Step 1)" — возврат назад - "Далее → (Step 3) [пропустить]" — авто-заполнение eventType, incidentDate, transportNumber **Step 3 (Payment):** - "← Назад (Step 2)" — возврат назад - "✅ Автоподтверждение телефона [dev]" — автозаполнение всех полей + setIsPhoneVerified(true) - "🚀 Отправить [пропустить]" — автозаполнение + submit --- ## 🛠️ Технические изменения ### Файл: `frontend/src/components/form/Step1Policy.tsx` #### Изменение 1: Динамические кнопки в модалке OCR **Было:** ```typescript footer={[ ]} ``` **Стало:** ```typescript footer={ocrModalContent === 'loading' ? null : ocrModalContent?.success ? [ ] : [ ] } ``` #### Изменение 2: DEV MODE панель ```typescript
🔧 DEV MODE - Быстрая навигация (без валидации)
``` ### Файл: `frontend/src/components/form/Step2Details.tsx` #### Полный рефакторинг! **Бэкап старой версии:** `Step2Details.OLD_MANUAL_INPUT.tsx` **Новая структура:** 1. **`DOCUMENT_CONFIGS`** — конфигурация документов для каждого типа события: ```typescript const DOCUMENT_CONFIGS = { delay_flight: [ { name: "Посадочный талон ИЛИ Билет", field: "boarding_or_ticket", file_type: "flight_delay_boarding_or_ticket", required: true, maxFiles: 1, description: "Посадочный талон (boarding pass) или билет (ticket/booking)", aiPromptFocus: "Извлеки: номер рейса, дату, маршрут, ФИО пассажира, время вылета" }, // ... остальные документы ], cancel_flight: [...], // ... остальные типы событий }; ``` 2. **Пошаговая загрузка документов:** ```typescript const [currentDocIndex, setCurrentDocIndex] = useState(0); const currentDoc = requiredDocs[currentDocIndex]; // После успешной загрузки if (currentDocIndex < requiredDocs.length - 1) { setCurrentDocIndex(prev => prev + 1); } else { // Все документы загружены onNext(); } ``` 3. **Модалка обработки для каждого документа:** ```typescript {currentDocIndex < requiredDocs.length - 1 ? 'Продолжить к следующему документу →' : 'Далее (Step 3) →' } ]} > {processingModalContent === 'loading' ? (

Обрабатываем документ...

) : (
{JSON.stringify(processingModalContent, null, 2)}
)}
``` 4. **SSE для каждого документа с уникальным `event_type`:** ```typescript const eventSource = new EventSource( `${API_BASE_URL}/events/${claimId}?event_type=${currentDoc.file_type}_processed` ); eventSource.onmessage = (event) => { const result = JSON.parse(event.data); if (result.event_type === `${currentDoc.file_type}_processed`) { setProcessingModalContent(result.data); } }; ``` #### DEV MODE панель: ```typescript
``` ### Файл: `frontend/src/components/form/Step3Payment.tsx` #### DEV MODE панель (3 кнопки): ```typescript ``` --- ## 🐛 Проблемы и их решения ### Проблема 1: Синтаксические ошибки на фронте **Симптом:** ``` чета шляпа у нас на фронте ``` **Диагностика:** - Пользователь сообщил "что то не того" - Проверка файлов показала **дублирующийся код** после закрывающих тегов компонентов **Найденные проблемы:** 1. **`Step1Policy.tsx`** (строки 659-820): - Дублирован весь DEV MODE блок после `` компонента - Код был просто скопирован повторно 2. **`Step3Payment.tsx`** (после строки 381): - Дублирован обрезанный фрагмент DEV панели - Неполный JSX **Решение:** ```bash # Удалены дублирующиеся блоки # Step1Policy.tsx: строки 659-820 удалены # Step3Payment.tsx: строки после 381 удалены # Rebuild frontend docker-compose build frontend docker-compose up -d frontend ``` **Коммиты:** - `2999951` - fix: Удалён дублирующийся код в Step1Policy.tsx - `1207222` - fix: Удалён дублирующийся код в Step3Payment.tsx ### Проблема 2: PostgreSQL INSERT не возвращает данные в n8n **Симптом:** ```json { "s3_url": null, "file_id": null, "error": { "message": "422 - \"{\\\"detail\\\":[{\\\"type\\\":\\\"string_type\\\",\\\"loc\\\":[\\\"body\\\",\\\"file_url\\\"],\\\"msg\\\":\\\"Input should be a valid string\\\",\\\"input\\\":null}]}\"" } } ``` **Причина:** 1. `INSERT INTO claim_files` не вернул `file_id` и `s3_url` 2. Выяснилось: запись в `claims` с данным `claim_number` не существует 3. Foreign key `claim_id` не может быть установлен → INSERT падает 4. `file_size` передан как `"4.47 MB"` вместо числа в байтах **Решение:** Создан UPSERT запрос с CTE (Common Table Expression): ```sql WITH upserted_claim AS ( INSERT INTO claims ( claim_number, voucher, session_id, status, created_at, updated_at ) VALUES ( $1, $2, $3, 'draft', NOW(), NOW() ) ON CONFLICT (claim_number) DO UPDATE SET updated_at = NOW(), voucher = COALESCE(EXCLUDED.voucher, claims.voucher) RETURNING id as claim_id ) INSERT INTO claim_files ( claim_id, file_type, original_name, s3_key, s3_url, file_size, mime_type, ocr_status, uploaded_at ) SELECT upserted_claim.claim_id, $4, $5, $6, $7, $8, $9, 'pending', NOW() FROM upserted_claim RETURNING id as file_id, s3_url, ocr_status; ``` **Параметры:** ```javascript [ claim_number, // $1 voucher, // $2 session_id, // $3 file_type, // $4 original_name, // $5 s3_key, // $6 s3_url, // $7 file_size, // $8 (число в байтах!) mime_type // $9 ] ``` **Преимущества:** - ✅ Атомарная операция - ✅ Идемпотентность (можно запускать повторно) - ✅ Всегда создаёт `claims` если его нет - ✅ Обновляет `updated_at` если уже есть - ✅ Возвращает `file_id` и `s3_url` для следующих шагов --- ## ✅ Тестирование ### Тест 1: Загрузка билета на отмену рейса **Файл:** "Билет Романова.pdf" **Claim ID:** CLM-2025-10-28-33ID32 **file_type:** `flight_cancel_ticket` **Результат Redis:** ```json { "claim_id": "CLM-2025-10-28-33ID32", "event": { "event_type": "flight_cancel_ticket_processed", "status": "completed", "message": "✅ Документ обработан: flight_cancel_ticket", "data": { "output": { "is_flight_doc": "yes", "document_type": "e-ticket", "ticket_number": "2222411714956", "passengers": [{ "full_name": "ROMANOVA ANASTASIIA", "doc_number": "774099576" }], "itinerary": [{ "flight_number": "A4-6025", "departure": { "airport_iata": "MRV", "date_local": "2025-09-30", "time_local": "16:25" }, "arrival": { "airport_iata": "TLV", "time_local": "20:00" } }] } } } } ``` **Backend лог:** ``` 16:46:29 - 📥 Received message type: message 16:46:29 - 📦 Raw event data: {"claim_id":"CLM-2025-10-28-33ID32",...} 16:46:29 - 📦 Unwrapped n8n Redis format for CLM-2025-10-28-33ID32 16:46:29 - 📤 Sending event to client: completed 16:46:29 - ✅ Task CLM-2025-10-28-33ID32 finished, closing SSE ``` **Результат:** ✅ Полный успех! - S3 upload ✅ - PostgreSQL UPSERT ✅ - OCR/AI обработка ✅ - Redis publish ✅ - Backend SSE ✅ - Frontend получил данные ✅ --- ## 📊 Архитектура Step 2 (новая) ``` ┌─────────────────────────────────────────────────────────────┐ │ Step 2: Details (NEW) │ │ │ │ 1. Выбор типа события (eventType) │ │ ↓ │ │ 2. DOCUMENT_CONFIGS определяет список документов │ │ ↓ │ │ 3. Пошаговая загрузка каждого документа: │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ Документ 1: Посадочный талон │ │ │ │ - Upload компонент │ │ │ │ - POST /upload → n8n webhook │ │ │ │ - file_type: "flight_delay_boarding_or_ticket" │ │ │ │ - SSE: event_type = "..._processed" │ │ │ │ - Модалка: "Обрабатываем..." │ │ │ │ - Результат: extracted data │ │ │ │ - Кнопка: "Продолжить к следующему" │ │ │ └────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ Документ 2: Подтверждение задержки │ │ │ │ - (аналогично) │ │ │ │ - file_type: "flight_delay_confirmation" │ │ │ │ - Может быть до 3 файлов │ │ │ │ - Кнопка: "Далее (Step 3)" │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ 4. После загрузки всех документов → Step 3 │ └─────────────────────────────────────────────────────────────┘ ``` ### Data Flow для одного документа: ``` Frontend n8n Backend Redis │ │ │ │ │ POST /upload │ │ │ ├────────────────────>│ │ │ │ {claim_id, │ │ │ │ file_type, │ │ │ │ file} │ │ │ │ │ │ │ │ SSE connect │ │ │ ├────────────────────────────────────────────>│ │ │ /events/CLM-XXX? │ │ │ │ event_type= │ │ │ │ flight_..._processed│ │ │ │ │ │ │ │ │ 1. Upload to S3 │ │ │ │ 2. UPSERT claims │ │ │ │ 3. INSERT claim_files │ │ │ │ 4. OCR Service │ │ │ │ 5. AI Vision │ │ │ │ 6. PUBLISH │ │ │ ├────────────────────────────────────────────>│ │ │ {event_type: │ │ │ │ "..._processed", │ │ │ │ data: {...}} │ │ │ │ │ │ │ │ │<──────────────────┤ │ │ │ SUBSCRIBE │ │ │ │ ocr_events:CLM-XXX │ │<────────────────────────────────────────────┤ │ │ SSE: data: {event_type, data} │ │ │ │ │ │ │ Show modal: │ │ │ │ "✅ Обработано" │ │ │ │ Display data │ │ │ │ │ │ │ │ User clicks │ │ │ │ "Continue" → │ │ │ │ next document │ │ │ │ (or Step 3) │ │ │ ``` --- ## 🎯 Логика обработки результатов AI (спроектирована) ### Предложенная структура валидации: ```typescript const handleOcrResult = (event) => { const { output } = event.data; // Проверка 1: Это правильный тип документа? if (output.is_flight_doc !== "yes") { return { success: false, message: "❌ Это не авиадокумент" }; } // Проверка 2: Извлечены ли критичные данные? const firstFlight = output.itinerary?.[0]; const criticalFields = { flightNumber: firstFlight?.flight_number, departureDate: firstFlight?.departure?.date_local, departureAirport: firstFlight?.departure?.airport_iata, arrivalAirport: firstFlight?.arrival?.airport_iata, passengerName: output.passengers?.[0]?.full_name }; const missing = Object.entries(criticalFields) .filter(([_, value]) => !value) .map(([key]) => key); if (missing.length === 0) { return { success: true, message: "✅ Билет распознан успешно!" }; } else { return { success: "partial", message: "⚠️ Билет распознан, но не хватает данных", missingFields: missing }; } }; ``` ### 3 сценария UI: **SUCCESS:** Все данные извлечены - ✅ Показать извлечённые данные - Кнопка: "Продолжить к следующему документу →" **PARTIAL:** Документ валидный, но данные неполные - ⚠️ Показать что извлечено + что отсутствует - 3 кнопки: 1. "📸 Загрузить документ лучшего качества" 2. "✍️ Ввести недостающие данные вручную" 3. "Продолжить с доступными данными" **FAIL:** Неправильный тип документа - ❌ Ошибка - 2 кнопки: 1. "Загрузить другой файл" 2. "Ввести данные вручную" --- ## 📝 Git Commits ```bash # Commit history (от старого к новому) 6fe1459 - backup: Сохранён старый Step2Details с ручным вводом полей 122af07 - feat: Умная форма Step2 с автоматическим распознаванием документов 9084d75 - feat: Пошаговая загрузка документов с модалкой на Step 2 2999951 - fix: Удалён дублирующийся код в Step1Policy.tsx 1207222 - fix: Удалён дублирующийся код в Step3Payment.tsx ``` **Push:** ✅ `origin/main` (все коммиты) --- ## 📈 Метрики **Время выполнения сессии:** ~4 часа **Количество коммитов:** 5 **Изменённых файлов:** 4 (Step1Policy, Step2Details, Step2Details.OLD, Step3Payment) **Строк добавлено:** ~800 **Строк удалено:** ~200 (дубликаты) + ~400 (рефакторинг Step2) **Frontend rebuilds:** 3 **Тестовых загрузок:** 3 **Redis событий обработано:** 3 --- ## 🔗 Ссылки - Frontend: http://147.45.146.17:5173 - Backend API: http://localhost:8100 - Gitea: http://147.45.146.17:3002/negodiy/erv-platform - n8n: http://147.45.146.17:5678 - N8N SQL Queries: `/erv_platform/N8N_SQL_QUERIES.md` --- ## 📝 Важные заметки ### Redis Password (обновлено) ``` Host: crm.clientright.ru Port: 6379 Password: CRM_Redis_Pass_2025_Secure! (из /etc/redis/redis.conf) ``` ### PostgreSQL UPSERT для n8n Сохранён в `N8N_SQL_QUERIES.md` для использования в webhook nodes. ### Структура `file_type` → `event_type` ``` file_type: "flight_cancel_ticket" event_type: "flight_cancel_ticket_processed" Формула: event_type = file_type + "_processed" ``` ### DEV MODE Все три шага имеют панель для быстрой навигации без заполнения форм — ускоряет разработку и тестирование. --- **Статус:** ✅ Успешно завершено **Автор:** AI Assistant (Claude Sonnet 4.5) **Дата:** 28 октября 2025, 17:00 MSK