# 📋 Лог сессии: Исправление 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
Обрабатываем документ...
{JSON.stringify(processingModalContent, null, 2)}