Files
aiform_dev/SESSION_LOG_2025-10-29.md

22 KiB
Raw Blame History

📋 Лог сессии: Рефакторинг визарда на динамические шаги

Дата: 29 октября 2025 (12:00 - 15:00 MSK)
Задача: Переделка визарда - каждый документ отдельным шагом (Вариант B)
Статус: Успешно завершено


🎯 Основная задача

Переделать структуру визарда так, чтобы каждый документ был отдельным шагом в прогресс-баре:

Было (inline документы):

[1. Полис] → [2. Детали + все документы] → [3. Оплата]

Стало (каждый документ = шаг):

[1. Полис] → [2. Тип] → [3. Док 1] → [4. Док 2] → [5. Док 3] → [N. Оплата]

Выполненные задачи

1. Создан Step2EventType.tsx

Назначение: Выбор типа страхового случая

Функционал:

  • Выпадающий список с иконками (✈️, 🚂, ⛴️)
  • 7 типов событий: delay_flight, cancel_flight, miss_connection, emergency_landing, delay_train, cancel_train, delay_ferry
  • Alert с подтверждением выбора
  • DEV MODE кнопка для быстрого выбора "Отмена рейса"

Файл: frontend/src/components/form/Step2EventType.tsx


2. Создан StepDocumentUpload.tsx

Назначение: Универсальный компонент для загрузки одного документа

Функционал:

  • Прогресс-бар: "Документ X из Y" + процент завершения
  • Upload компонент для выбора файлов
  • Автоматическая загрузка на n8n webhook
  • SSE для получения результатов AI обработки
  • Модалка "Обрабатываем документ..." с результатами
  • Проверка isAlreadyUploaded для пропуска повторной загрузки
  • Кнопки: "Назад", "Загрузить", "Пропустить" (для необязательных)
  • DEV MODE: "Назад" и "Пропустить [dev]"

Props:

{
  documentConfig: DocumentConfig;  // Конфигурация документа
  formData: any;                   // Данные формы
  updateFormData: (data) => void;  // Обновление данных
  onNext: () => void;              // Следующий шаг
  onPrev: () => void;              // Предыдущий шаг
  isLastDocument: boolean;         // Последний документ?
  currentDocNumber: number;        // Номер текущего документа
  totalDocs: number;               // Всего документов
}

Файл: frontend/src/components/form/StepDocumentUpload.tsx


3. Создан constants/documentConfigs.ts

Назначение: Централизованная конфигурация документов для всех типов событий

Структура:

export interface DocumentConfig {
  name: string;           // Название документа
  field: string;          // Поле в formData
  file_type: string;      // Уникальный идентификатор для n8n
  required: boolean;      // Обязательный?
  maxFiles: number;       // Максимум файлов
  description: string;    // Описание для пользователя
}

export const DOCUMENT_CONFIGS: Record<string, DocumentConfig[]> = {
  delay_flight: [...],
  cancel_flight: [...],
  miss_connection: [...],
  delay_train: [...],
  cancel_train: [...],
  delay_ferry: [...],
  emergency_landing: [...]
};

Пример для отмены рейса:

cancel_flight: [
  {
    name: "Билет",
    field: "ticket",
    file_type: "flight_cancel_ticket",
    required: true,
    maxFiles: 1,
    description: "Ticket/booking confirmation"
  },
  {
    name: "Уведомление об отмене",
    field: "cancellation_notice",
    file_type: "flight_cancel_notice",
    required: true,
    maxFiles: 3,
    description: "Email, SMS или скриншот из приложения АК"
  }
]

Функции:

  • getDocumentsForEventType(eventType) - получить список документов
  • getTotalDocumentsCount(eventType) - количество документов

Файл: frontend/src/constants/documentConfigs.ts


4. Переделан ClaimForm.tsx на динамические шаги

Изменения:

4.1. Импорты

import { useState, useMemo, useCallback } from 'react';
import Step2EventType from '../components/form/Step2EventType';
import StepDocumentUpload from '../components/form/StepDocumentUpload';
import { getDocumentsForEventType } from '../constants/documentConfigs';

4.2. FormData интерфейс

interface FormData {
  // Шаг 1: Policy
  voucher: string;
  claim_id?: string;
  session_id?: string;
  
  // Шаг 2: Event Type
  eventType?: string;
  
  // Шаги 3+: Documents
  documents?: Record<string, {
    uploaded: boolean;
    data: any;
    file_type: string;
    skipped?: boolean;
  }>;
  
  // Последний шаг: Payment
  fullName?: string;
  email?: string;
  phone?: string;
  paymentMethod?: string;
  // ...
}

4.3. Динамическое определение документов

const documentConfigs = formData.eventType 
  ? getDocumentsForEventType(formData.eventType) 
  : [];
const totalDocumentSteps = documentConfigs.length;

4.4. useCallback для функций навигации

const nextStep = useCallback(() => {
  console.log('⏩ nextStep called');
  setCurrentStep((prev) => {
    console.log('📍 Current step:', prev, '→ Next:', prev + 1);
    return prev + 1;
  });
}, []);

const prevStep = useCallback(() => {
  console.log('⏪ prevStep called');
  setCurrentStep((prev) => {
    console.log('📍 Current step:', prev, '→ Prev:', prev - 1);
    return prev - 1;
  });
}, []);

const updateFormData = useCallback((data: Partial<FormData>) => {
  setFormData((prev) => ({ ...prev, ...data }));
}, []);

Почему useCallback критично:

  • Без useCallback функции пересоздаются при каждом рендере
  • Компоненты получают новые ссылки → ререндер → closure захватывает старые значения
  • prevStep вызывался, но setCurrentStep не срабатывал

4.5. Динамическая генерация шагов через useMemo

const steps = useMemo(() => {
  const stepsArray: any[] = [];

  // Шаг 1: Policy (всегда)
  stepsArray.push({
    title: 'Проверка полиса',
    description: 'Полис ERV',
    content: <Step1Policy ... />
  });

  // Шаг 2: Event Type (всегда)
  stepsArray.push({
    title: 'Тип события',
    description: 'Выбор случая',
    content: <Step2EventType ... />
  });

  // Шаги 3+: Documents (динамически)
  if (formData.eventType && documentConfigs.length > 0) {
    documentConfigs.forEach((docConfig, index) => {
      stepsArray.push({
        title: `Документ ${index + 1}`,
        description: docConfig.name,
        content: <StepDocumentUpload ... />
      });
    });
  }

  // Последний шаг: Payment (всегда)
  stepsArray.push({
    title: 'Оплата',
    description: 'Контакты и выплата',
    content: <Step3Payment ... />
  });

  return stepsArray;
}, [formData, documentConfigs, isPhoneVerified, claimId, sessionId, 
    nextStep, prevStep, updateFormData, handleSubmit, 
    setIsPhoneVerified, addDebugEvent]);

4.6. Прогресс-бар с описаниями

<Steps current={currentStep} className="steps">
  {steps.map((item, index) => (
    <Step 
      key={`step-${index}`} 
      title={item.title} 
      description={item.description}
    />
  ))}
</Steps>

Файл: frontend/src/pages/ClaimForm.tsx


5. Бэкап старых версий

  • Step2Details.OLD_MANUAL_INPUT.tsx - версия с ручным вводом полей
  • Step2Details.OLD_WIZARD_INLINE.tsx - версия с inline загрузкой документов

🐛 Исправленные проблемы

Проблема 1: Неправильный URL n8n webhook

Симптом:

POST https://n8n.clientright.ru/webhook/erv-upload 
net::ERR_NAME_NOT_RESOLVED

Причина: Использовался несуществующий домен n8n.clientright.ru

Решение:

- https://n8n.clientright.ru/webhook/erv-upload
+ https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95

Commit: 4e5bc76


Проблема 2: Неправильная структура FormData

Симптом: n8n получал данные в неправильном формате

Было:

formDataToSend.append('files', file);  // множественное число
// Нет filename и upload_timestamp

Стало:

formDataToSend.append('claim_id', claimId);
formDataToSend.append('file_type', documentConfig.file_type);
formDataToSend.append('filename', file.name);              // ✅
formDataToSend.append('voucher', formData.voucher);
formDataToSend.append('session_id', sessionId);
formDataToSend.append('upload_timestamp', new Date().toISOString()); // ✅
formDataToSend.append('file', file.originFileObj);         // ✅ единственное число

Commit: 4ad6b78


Проблема 3: Ложные ошибки SSE в консоли

Симптом:

❌ SSE connection error: Event {...}

Причина: Backend закрывает SSE после отправки результата → браузер триггерит onerror → выводится красная ошибка

Решение:

eventSource.onerror = (error) => {
  console.log('🔌 SSE connection closed');
  
  setProcessingModalContent((prev) => {
    if (prev && prev !== 'loading') {
      console.log('✅ SSE закрыто после получения результата - всё ОК');
      return prev; // Не затираем результат
    }
    console.error('❌ SSE ошибка: не получили данные', error);
    return { success: false, message: 'Ошибка подключения' };
  });
};

Commit: 67f054d


Проблема 4: Неправильный расчёт прогресса

Симптом: "Документ 2/2" показывал "100%" ДО загрузки

Было:

percent = (currentDocNumber / totalDocs) * 100
// Документ 2/2 = 100% (неправильно!)

Стало:

percent = ((currentDocNumber - 1) / totalDocs) * 100
// Документ 1/2: 0% (до) → 50% (после)
// Документ 2/2: 50% (до) → 100% (после)

Commit: 145a9bd


Проблема 5: Кнопки "Назад" не кликабельны

Симптом: Кнопки "Назад" серые (disabled), хотя в коде disabled не было

Решение: Явно установил disabled={false} и добавил логирование:

<Button 
  onClick={() => {
    console.log('🔙 Кнопка Назад нажата');
    onPrev();
  }} 
  size="large"
  disabled={false}
>
   Назад
</Button>

Commit: d727b74


Проблема 6: Навигация назад не работает

Симптом: Клик регистрируется в консоли, но currentStep не изменяется

Причина:

  • Функции nextStep, prevStep пересоздавались при каждом рендере
  • Компоненты получали новые ссылки → ререндер
  • Closure захватывал старое значение currentStep

Решение: Обернул в useCallback + functional update:

const prevStep = useCallback(() => {
  console.log('⏪ prevStep called');
  setCurrentStep((prev) => {
    console.log('📍 Current step:', prev, '→ Prev:', prev - 1);
    return prev - 1;  // Functional update!
  });
}, []);

Commit: 9f39847


📦 Git История

# Commit history (от старого к новому)
6fe1459 - backup: Сохранён старый Step2Details с ручным вводом полей
122af07 - feat: Умная форма Step2 с автоматическим распознаванием документов
9084d75 - feat: Пошаговая загрузка документов с модалкой на Step 2
2999951 - fix: Удалён дублирующийся код в Step1Policy.tsx
1207222 - fix: Удалён дублирующийся код в Step3Payment.tsx
6c19392 - docs: Обновлён лог сессии - добавлена вторая часть (умная форма Step 2)

# Сессия 29 октября (рефакторинг на динамические шаги)
1f25301 - feat: Переделан визард на динамические шаги - каждый документ отдельный Step
f06105d - fix: Исправлена работа Upload и кнопки Назад в StepDocumentUpload
4e5bc76 - fix: Исправлен URL n8n webhook на правильный домен
4ad6b78 - fix: Исправлена структура FormData для загрузки документов
67f054d - fix: Улучшено логирование SSE - убраны ложные ошибки
145a9bd - fix: Исправлен расчёт прогресса загрузки документов
d727b74 - fix: Явно установлен disabled=false для всех кнопок Назад
9f39847 - fix: Исправлена навигация назад через useCallback

Push: origin/main (все коммиты)


🎨 Примеры визуализации

Пример 1: Отмена рейса (2 документа)

Шаг 1: Проверка полиса
  └─ Полис ERV

Шаг 2: Тип события
  └─ ✈️❌ Отмена авиарейса

Шаг 3: Документ 1 (0% → 50%)
  └─ Билет
  └─ Upload → n8n → AI → SSE → Модалка с результатами

Шаг 4: Документ 2 (50% → 100%)
  └─ Уведомление об отмене
  └─ Upload → n8n → AI → SSE → Модалка с результатами

Шаг 5: Оплата
  └─ Контакты и выплата

Пример 2: Пропуск стыковки (3 документа, 1 опциональный)

[1.Полис] → [2.Тип] → [3.Посадочный талон прибытия] → 
[4.Билет отправления] → [5.Доказательство задержки (опционально)] → 
[6.Оплата]

🔧 Технические детали

Data Flow для одного документа

Frontend (StepDocumentUpload)
│
├─ User selects file
│  └─ Upload component → setFileList([file])
│
├─ User clicks "Загрузить и обработать"
│  └─ handleUpload() called
│
├─ FormData creation
│  ├─ claim_id
│  ├─ file_type (уникальный для каждого документа)
│  ├─ filename
│  ├─ voucher
│  ├─ session_id
│  ├─ upload_timestamp
│  └─ file (originFileObj)
│
├─ POST to n8n webhook
│  └─ https://n8n.clientright.pro/webhook/7e2abc64-...
│
├─ SSE connection opens
│  └─ GET /events/{claim_id}?event_type={file_type}_processed
│
├─ Show modal "Обрабатываем документ..."
│  └─ Spin + "Извлекаем данные с помощью AI"
│
│  [n8n workflow]
│  ├─ Upload to S3
│  ├─ PostgreSQL UPSERT (claims + claim_files)
│  ├─ OCR Service (147.45.146.17:8001)
│  ├─ AI Vision (OpenRouter Gemini 2.0 Flash)
│  └─ Redis PUBLISH to ocr_events:{claim_id}
│
├─ Backend receives Redis message
│  └─ SSE sends event to frontend
│
├─ Frontend receives SSE message
│  └─ eventSource.onmessage
│     └─ setProcessingModalContent(result.data)
│
├─ Modal shows results
│  ├─ ✅ Документ обработан
│  ├─ JSON with extracted data
│  └─ Button: "Продолжить к следующему документу →"
│
├─ User clicks "Продолжить"
│  └─ handleContinue()
│     ├─ setProcessingModalVisible(false)
│     ├─ setUploading(false)
│     ├─ eventSource.close()
│     └─ onNext() → nextStep() → setCurrentStep(prev => prev + 1)
│
└─ Next document step renders (or Payment if last)

Уникальные file_type для n8n

Событие Документ file_type event_type
Задержка рейса Талон/билет flight_delay_boarding_or_ticket flight_delay_boarding_or_ticket_processed
Задержка рейса Подтверждение flight_delay_confirmation flight_delay_confirmation_processed
Отмена рейса Билет flight_cancel_ticket flight_cancel_ticket_processed
Отмена рейса Уведомление flight_cancel_notice flight_cancel_notice_processed
Пропуск стыковки Талон прибытия connection_arrival_boarding connection_arrival_boarding_processed
Пропуск стыковки Талон/билет отправления connection_departure_boarding_or_ticket connection_departure_boarding_or_ticket_processed
Пропуск стыковки Доказательство задержки connection_delay_proof connection_delay_proof_processed

Формула: event_type = file_type + "_processed"


📊 Метрики

Время выполнения сессии: ~3 часа
Количество коммитов: 9
Созданных файлов: 3

  • Step2EventType.tsx
  • StepDocumentUpload.tsx
  • constants/documentConfigs.ts

Изменённых файлов: 2

  • ClaimForm.tsx (полная переделка логики)
  • StepDocumentUpload.tsx (множество фиксов)

Строк добавлено: ~1500
Строк удалено: ~50
Frontend rebuilds: 9
Тестовых загрузок: 5


🔗 Ссылки


📝 Важные заметки

Redis Configuration

Host: crm.clientright.ru
Port: 6379
Password: CRM_Redis_Pass_2025_Secure!
Channel pattern: ocr_events:{claim_id}

DEV MODE во всех шагах

Для ускорения разработки и тестирования добавлены кнопки быстрой навигации:

  • Step 1: "Далее → (Step 2) [пропустить]"
  • Step 2: "Далее → [Отмена рейса]"
  • Step 3+: "Пропустить [dev] →"
  • Step Payment: " Автоподтверждение телефона [dev]", "🚀 Отправить [пропустить]"

Ant Design Warnings (не критично)

В консоли показываются deprecation warnings:

  • headStylestyles.header
  • bodyStylestyles.body
  • Timeline.Itemitems

Эти warning в DebugPanel.tsx - не влияют на работу, можно исправить позже.


Итоговый результат

Что работает:

  1. Динамические шаги на основе выбранного eventType
  2. Каждый документ загружается на отдельном шаге
  3. Прогресс-бар показывает все шаги с описаниями
  4. Upload → n8n → S3 → PostgreSQL → OCR → AI → Redis → SSE
  5. Модалка показывает процесс обработки и результаты
  6. Навигация вперёд/назад работает корректно
  7. DEV MODE кнопки на всех шагах
  8. Логирование в консоль для отладки

Архитектура:

ClaimForm (главный компонент)
├─ useMemo для динамической генерации steps
├─ useCallback для стабильных функций навигации
│
├─ Step 1: Step1Policy
│  └─ Загрузка и OCR полиса
│
├─ Step 2: Step2EventType
│  └─ Выбор типа события
│
├─ Steps 3...N-1: StepDocumentUpload (динамически)
│  └─ Для каждого документа из DOCUMENT_CONFIGS
│     ├─ Прогресс: "Документ X из Y"
│     ├─ Upload компонент
│     ├─ POST to n8n → S3 → DB → OCR → AI
│     ├─ SSE для получения результата
│     └─ Модалка с извлечёнными данными
│
└─ Step N: Step3Payment
   └─ Контакты и выплата

Статус: Успешно завершено
Автор: AI Assistant (Claude Sonnet 4.5)
Дата: 29 октября 2025, 15:00 MSK