docs: Обновлён лог сессии - добавлена вторая часть (умная форма Step 2)
This commit is contained in:
@@ -354,3 +354,710 @@ 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={[
|
||||
<Button key="close" onClick={() => setOcrModalVisible(false)}>
|
||||
Закрыть
|
||||
</Button>
|
||||
]}
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```typescript
|
||||
footer={ocrModalContent === 'loading' ? null :
|
||||
ocrModalContent?.success ? [
|
||||
<Button key="next" type="primary" onClick={() => {
|
||||
setOcrModalVisible(false);
|
||||
onNext(); // Переход на следующий шаг
|
||||
}}>
|
||||
Продолжить →
|
||||
</Button>
|
||||
] : [
|
||||
<Button key="retry" type="primary" onClick={() => {
|
||||
setOcrModalVisible(false);
|
||||
setFileList([]); // Очищаем список файлов
|
||||
setPolicyNotFound(true); // Показываем форму загрузки снова
|
||||
}}>
|
||||
Загрузить другой файл
|
||||
</Button>
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Изменение 2: DEV MODE панель
|
||||
|
||||
```typescript
|
||||
<div style={{
|
||||
marginTop: 24,
|
||||
padding: 16,
|
||||
background: '#f0f0f0',
|
||||
borderRadius: 8,
|
||||
border: '2px dashed #999'
|
||||
}}>
|
||||
<div style={{ marginBottom: 8, fontSize: 12, color: '#666', fontWeight: 'bold' }}>
|
||||
🔧 DEV MODE - Быстрая навигация (без валидации)
|
||||
</div>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
const devData = {
|
||||
voucher: 'E1000-123456789',
|
||||
claim_id: `CLM-DEV-${Math.random().toString(36).substr(2, 6).toUpperCase()}`,
|
||||
};
|
||||
updateFormData(devData);
|
||||
onNext();
|
||||
}}
|
||||
>
|
||||
Далее → (Step 2) [пропустить]
|
||||
</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Файл: `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
|
||||
<Modal
|
||||
title="Обработка документа"
|
||||
visible={processingModalVisible}
|
||||
footer={processingModalContent === 'loading' ? null : [
|
||||
<Button type="primary" onClick={handleContinueAfterProcessing}>
|
||||
{currentDocIndex < requiredDocs.length - 1
|
||||
? 'Продолжить к следующему документу →'
|
||||
: 'Далее (Step 3) →'
|
||||
}
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
{processingModalContent === 'loading' ? (
|
||||
<div style={{textAlign: 'center', padding: 24}}>
|
||||
<Spin size="large" />
|
||||
<p>Обрабатываем документ...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<Alert type="success" message="✅ Документ обработан" />
|
||||
<pre>{JSON.stringify(processingModalContent, null, 2)}</pre>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
```
|
||||
|
||||
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
|
||||
<div style={{ marginTop: 24, padding: 16, background: '#f0f0f0' }}>
|
||||
<Button onClick={onPrev}>← Назад (Step 1)</Button>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
const devData = { eventType: 'delay_flight', incidentDate: dayjs(), transportNumber: 'TEST123' };
|
||||
updateFormData(devData);
|
||||
onNext();
|
||||
}}
|
||||
>
|
||||
Далее → (Step 3) [пропустить]
|
||||
</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Файл: `frontend/src/components/form/Step3Payment.tsx`
|
||||
|
||||
#### DEV MODE панель (3 кнопки):
|
||||
```typescript
|
||||
<Button onClick={onPrev}>← Назад (Step 2)</Button>
|
||||
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
setIsPhoneVerified(true);
|
||||
const devData = {
|
||||
fullName: 'Тест Тестов',
|
||||
email: 'test@test.ru',
|
||||
phone: '+79991234567',
|
||||
paymentMethod: 'sbp',
|
||||
bankName: 'sberbank',
|
||||
};
|
||||
updateFormData(devData);
|
||||
message.success('DEV: Телефон автоматически подтверждён');
|
||||
}}
|
||||
>
|
||||
✅ Автоподтверждение телефона [dev]
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setIsPhoneVerified(true);
|
||||
const devData = {...};
|
||||
updateFormData(devData);
|
||||
onSubmit();
|
||||
}}
|
||||
>
|
||||
🚀 Отправить [пропустить]
|
||||
</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Проблемы и их решения
|
||||
|
||||
### Проблема 1: Синтаксические ошибки на фронте
|
||||
|
||||
**Симптом:**
|
||||
```
|
||||
чета шляпа у нас на фронте
|
||||
```
|
||||
|
||||
**Диагностика:**
|
||||
- Пользователь сообщил "что то не того"
|
||||
- Проверка файлов показала **дублирующийся код** после закрывающих тегов компонентов
|
||||
|
||||
**Найденные проблемы:**
|
||||
|
||||
1. **`Step1Policy.tsx`** (строки 659-820):
|
||||
- Дублирован весь DEV MODE блок после `</div>` компонента
|
||||
- Код был просто скопирован повторно
|
||||
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user