Files
aiform_dev/SESSION_LOG_2025-10-29.md

646 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 📋 Лог сессии: Рефакторинг визарда на динамические шаги
**Дата:** 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:**
```typescript
{
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
**Назначение:** Централизованная конфигурация документов для всех типов событий
**Структура:**
```typescript
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: [...]
};
```
**Пример для отмены рейса:**
```typescript
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. Импорты
```typescript
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 интерфейс
```typescript
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. Динамическое определение документов
```typescript
const documentConfigs = formData.eventType
? getDocumentsForEventType(formData.eventType)
: [];
const totalDocumentSteps = documentConfigs.length;
```
#### 4.4. useCallback для функций навигации
```typescript
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
```typescript
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. Прогресс-бар с описаниями
```typescript
<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`
**Решение:**
```diff
- https://n8n.clientright.ru/webhook/erv-upload
+ https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95
```
**Commit:** `4e5bc76`
---
### Проблема 2: Неправильная структура FormData
**Симптом:** n8n получал данные в неправильном формате
**Было:**
```javascript
formDataToSend.append('files', file); // множественное число
// Нет filename и upload_timestamp
```
**Стало:**
```javascript
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` → выводится красная ошибка
**Решение:**
```javascript
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%" ДО загрузки
**Было:**
```javascript
percent = (currentDocNumber / totalDocs) * 100
// Документ 2/2 = 100% (неправильно!)
```
**Стало:**
```javascript
percent = ((currentDocNumber - 1) / totalDocs) * 100
// Документ 1/2: 0% (до) → 50% (после)
// Документ 2/2: 50% (до) → 100% (после)
```
**Commit:** `145a9bd`
---
### Проблема 5: Кнопки "Назад" не кликабельны
**Симптом:** Кнопки "Назад" серые (disabled), хотя в коде `disabled` не было
**Решение:** Явно установил `disabled={false}` и добавил логирование:
```javascript
<Button
onClick={() => {
console.log('🔙 Кнопка Назад нажата');
onPrev();
}}
size="large"
disabled={false}
>
Назад
</Button>
```
**Commit:** `d727b74`
---
### Проблема 6: Навигация назад не работает
**Симптом:** Клик регистрируется в консоли, но `currentStep` не изменяется
**Причина:**
- Функции `nextStep`, `prevStep` пересоздавались при каждом рендере
- Компоненты получали новые ссылки → ререндер
- Closure захватывал старое значение `currentStep`
**Решение:** Обернул в `useCallback` + functional update:
```javascript
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 История
```bash
# 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
---
## 🔗 Ссылки
- **Frontend:** http://147.45.146.17:5173
- **Backend API:** http://localhost:8100
- **Gitea:** http://147.45.146.17:3002/negodiy/erv-platform
- **n8n Production:** https://n8n.clientright.pro
- **n8n Dev:** http://147.45.146.17:5678
- **n8n Webhook:** https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95
---
## 📝 Важные заметки
### 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:
- `headStyle``styles.header`
- `bodyStyle``styles.body`
- `Timeline.Item``items`
Эти 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