diff --git a/ticket_form/backend/app/api/claims.py b/ticket_form/backend/app/api/claims.py index 9a99d4df..75787f97 100644 --- a/ticket_form/backend/app/api/claims.py +++ b/ticket_form/backend/app/api/claims.py @@ -471,7 +471,7 @@ async def publish_ticket_form_description(payload: TicketFormDescriptionRequest) event = { "type": "ticket_form_description", "session_id": payload.session_id, - "claim_id": payload.claim_id, + "claim_id": payload.claim_id, # Опционально - может быть None "phone": payload.phone, "email": payload.email, "description": payload.problem_description.strip(), @@ -480,7 +480,7 @@ async def publish_ticket_form_description(payload: TicketFormDescriptionRequest) } logger.info( "📝 TicketForm description received", - extra={"session_id": payload.session_id, "claim_id": payload.claim_id}, + extra={"session_id": payload.session_id, "claim_id": payload.claim_id or "not_set"}, ) await redis_service.publish(channel, json.dumps(event, ensure_ascii=False)) logger.info( diff --git a/ticket_form/docs/CLAIMSAVE_PRIMARY_DRAFT_FIX.md b/ticket_form/docs/CLAIMSAVE_PRIMARY_DRAFT_FIX.md new file mode 100644 index 00000000..f3c43e9c --- /dev/null +++ b/ticket_form/docs/CLAIMSAVE_PRIMARY_DRAFT_FIX.md @@ -0,0 +1,335 @@ +# Исправление узла `claimsave` для сохранения первичного черновика + +## Проблемы + +1. **`claim_id` генерируется в другом workflow** - нужно использовать `session_id` для связи, `claim_id` генерировать позже +2. **Неправильный `sessionToken` в Code4** - используется `claim_id` вместо `session_token` +3. **Нет сохранения первичного черновика** - нужно сохранить сразу после генерации `wizard_plan` +4. **Данные из AI Agent1 и AI Agent13 не сохраняются** - они пригодятся, нужно их сохранить в черновик + +## Решение + +### 1. Исправить узел `Code4` (подготовка данных для Redis) + +**Текущий код (строка 459):** +```javascript +const sessionToken = $('Redis Trigger').first().json.message.claim_id +``` + +**Проблема:** Используется `claim_id` вместо `session_token` для Redis ключа. `claim_id` может быть недоступен или генерируется позже. + +**Исправленный код:** +```javascript +// Получаем session_token из разных источников (приоритет: Edit Fields11 > Redis Trigger) +const sessionToken = $('Edit Fields11').first().json.session_token + || $('Redis Trigger').first().json.message.session_id + || null; + +// Если session_token недоступен, генерируем временный ключ +if (!sessionToken) { + console.warn('⚠️ session_token не найден, используем временный ключ'); +} + +// Используем session_token для Redis ключа (claim_id будет сгенерирован позже) +const redisKey = `ocr_events:${sessionToken || 'temp-' + Date.now()}`; +``` + +### 2. Создать новый узел `claimsave_primary` (сохранение первичного черновика) + +**Позиция:** После узла `Code4`, перед `push_wizard1` + +**Назначение:** Сохранить первичный черновик сразу после генерации `wizard_plan` + +**SQL запрос:** +```sql +-- $1 = payload_json (jsonb) - полный payload с wizard_plan, problem_description, AI Agent1, AI Agent13 и т.д. +-- $2 = session_token (text) - сессия пользователя (используем для связи, claim_id генерируем позже) +-- $3 = unified_id (text, опционально) - unified_id пользователя + +WITH partial AS ( + SELECT + $1::jsonb AS p, + $2::text AS session_token_str, + NULLIF($3::text, '') AS unified_id_str +), + +-- Находим существующую запись по session_token или создаем новую +claim_lookup AS ( + SELECT + COALESCE( + (SELECT id FROM clpr_claims WHERE session_token = partial.session_token_str LIMIT 1), + gen_random_uuid() + ) AS claim_uuid + FROM partial +), + +-- Если записи нет, создаем её +claim_created AS ( + INSERT INTO clpr_claims ( + id, + session_token, + unified_id, + channel, + type_code, + status_code, + payload, + created_at, + updated_at, + expires_at + ) + SELECT + claim_lookup.claim_uuid, + partial.session_token_str, + partial.unified_id_str, + 'web_form', + COALESCE(partial.p->>'type_code', 'consumer'), + 'draft', + jsonb_build_object( + -- claim_id будет сгенерирован позже, пока NULL + 'claim_id', NULL, + 'problem_description', partial.p->>'problem_description', + 'wizard_plan', + CASE + WHEN partial.p->>'wizard_plan' IS NOT NULL + THEN (partial.p->>'wizard_plan')::jsonb + WHEN partial.p->'wizard_plan' IS NOT NULL AND jsonb_typeof(partial.p->'wizard_plan') = 'object' + THEN partial.p->'wizard_plan' + ELSE NULL + END, + 'answers_prefill', + CASE + WHEN partial.p->>'answers_prefill' IS NOT NULL + THEN (partial.p->>'answers_prefill')::jsonb + WHEN partial.p->'answers_prefill' IS NOT NULL AND jsonb_typeof(partial.p->'answers_prefill') = 'array' + THEN partial.p->'answers_prefill' + ELSE '[]'::jsonb + END, + 'coverage_report', + CASE + WHEN partial.p->>'coverage_report' IS NOT NULL + THEN (partial.p->>'coverage_report')::jsonb + WHEN partial.p->'coverage_report' IS NOT NULL AND jsonb_typeof(partial.p->'coverage_report') = 'object' + THEN partial.p->'coverage_report' + ELSE NULL + END, + -- Данные из AI Agent1 (факты) + 'ai_agent1_facts', + CASE + WHEN partial.p->'ai_agent1_facts' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent1_facts') = 'object' + THEN partial.p->'ai_agent1_facts' + ELSE NULL + END, + -- Данные из AI Agent13 (RAG ответ) + 'ai_agent13_rag', + CASE + WHEN partial.p->>'ai_agent13_rag' IS NOT NULL + THEN (partial.p->>'ai_agent13_rag')::jsonb + WHEN partial.p->'ai_agent13_rag' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent13_rag') = 'object' + THEN partial.p->'ai_agent13_rag' + ELSE NULL + END, + 'phone', partial.p->>'phone', + 'email', partial.p->>'email' + ), + now(), + now(), + now() + interval '14 days' + FROM partial, claim_lookup + WHERE NOT EXISTS ( + SELECT 1 FROM clpr_claims WHERE id = claim_lookup.claim_uuid + ) + ON CONFLICT (id) DO NOTHING + RETURNING id +), + +-- Получаем финальный UUID +claim_final AS ( + SELECT + CASE + WHEN EXISTS (SELECT 1 FROM claim_created) + THEN (SELECT id FROM claim_created LIMIT 1) + ELSE claim_lookup.claim_uuid + END AS claim_uuid + FROM claim_lookup +), + +-- Обновляем существующую запись (если есть) +upd AS ( + UPDATE clpr_claims c + SET + unified_id = COALESCE(partial.unified_id_str, c.unified_id), + payload = jsonb_set( + jsonb_set( + jsonb_set( + jsonb_set( + COALESCE(c.payload, '{}'::jsonb), + '{wizard_plan}', + COALESCE( + CASE + WHEN partial.p->>'wizard_plan' IS NOT NULL + THEN (partial.p->>'wizard_plan')::jsonb + WHEN partial.p->'wizard_plan' IS NOT NULL AND jsonb_typeof(partial.p->'wizard_plan') = 'object' + THEN partial.p->'wizard_plan' + ELSE NULL + END, + c.payload->'wizard_plan' + ), + true + ), + '{ai_agent1_facts}', + COALESCE( + CASE + WHEN partial.p->'ai_agent1_facts' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent1_facts') = 'object' + THEN partial.p->'ai_agent1_facts' + ELSE NULL + END, + c.payload->'ai_agent1_facts' + ), + true + ), + '{ai_agent13_rag}', + COALESCE( + CASE + WHEN partial.p->>'ai_agent13_rag' IS NOT NULL + THEN (partial.p->>'ai_agent13_rag')::jsonb + WHEN partial.p->'ai_agent13_rag' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent13_rag') = 'object' + THEN partial.p->'ai_agent13_rag' + ELSE NULL + END, + c.payload->'ai_agent13_rag' + ), + true + ), + '{problem_description}', + COALESCE(partial.p->>'problem_description', c.payload->>'problem_description'), + true + ), + updated_at = now(), + expires_at = now() + interval '14 days' + FROM partial, claim_final + WHERE c.id = claim_final.claim_uuid + AND EXISTS (SELECT 1 FROM claim_lookup WHERE claim_uuid = c.id) + RETURNING c.id, c.payload +) + +SELECT + (SELECT jsonb_build_object( + 'claim_id', u.id::text, + 'session_token', partial.session_token_str, + 'status_code', 'draft', + 'payload', COALESCE(u.payload, jsonb_build_object()) + ) + FROM claim_final cf, partial + LEFT JOIN upd u ON true + LIMIT 1) AS claim; +``` + +**Параметры в n8n:** +``` +$1 = {{ JSON.stringify({ + problem_description: $('Edit Fields16').first().json.chatInput, + wizard_plan: $('Code4').first().json.redis_value.wizard_plan, + answers_prefill: $('Code4').first().json.redis_value.answers_prefill, + coverage_report: $('Code4').first().json.redis_value.coverage_report, + // Данные из AI Agent1 (факты) + ai_agent1_facts: { + facts_short: $('пробрасываем факт фул и факт шорт1').first().json.facts_short, + facts_full: $('пробрасываем факт фул и факт шорт1').first().json.facts_full, + problem: $('пробрасываем факт фул и факт шорт1').first().json.problem + }, + // Данные из AI Agent13 (RAG ответ) + ai_agent13_rag: $('AI Agent13').first().json.output, + phone: $('Redis Trigger').first().json.message.phone, + email: $('Redis Trigger').first().json.message.email || null, + type_code: $('Code4').first().json.redis_value.wizard_plan?.case_type || 'consumer' +}) }} + +$2 = {{ $('Edit Fields11').first().json.session_token || $('Redis Trigger').first().json.message.session_id }} + +$3 = {{ $('Edit Fields10').first().json.unified_id || $('Redis Trigger').first().json.message.unified_id || null }} +``` + +### 3. Исправить узел `claimsave` (для последующих обновлений) + +**Текущий queryReplacement:** +``` +={{ $json.payload_partial_json }}, {{ $('Redis Trigger').item.json.message.claim_id }} +``` + +**Проблема:** Используется `claim_id` из `Redis Trigger`, который может быть недоступен. Также SQL ищет запись по `claim_id`, но на этапе первичного черновика `claim_id` может быть NULL. + +**Исправленный queryReplacement:** +``` +={{ $json.payload_partial_json }}, {{ $('Edit Fields11').first().json.session_token || $('Redis Trigger').first().json.message.session_id }} +``` + +**Также нужно обновить SQL в узле `claimsave`** - искать запись по `session_token` вместо `claim_id`: +```sql +-- Вместо: +WHERE payload->>'claim_id' = partial.claim_id_str + +-- Использовать: +WHERE session_token = partial.session_token_str +``` + +**Примечание:** Узел `claimsave` используется для последующих обновлений (после загрузки файлов, ответов пользователя и т.д.), поэтому он должен работать с уже существующим черновиком, найденным по `session_token`. + +## Порядок узлов в workflow + +1. `Redis Trigger` → получает событие +2. `get_claime_data1` → получает данные из Redis +3. `Edit Fields8` → извлекает поля из сообщения +4. `Merge2` → объединяет данные +5. `Get row(s) in sheet2` → получает шаги формы +6. `Edit Fields16` → подготавливает данные для AI +7. `AI Agent1` → извлекает факты (полный и короткий) +8. `пробрасываем факт фул и факт шорт1` → передает факты +9. `AI Agent13` → генерирует RAG ответ +10. `output_set1` → форматирует выход +11. `Edit Fields11` → подготавливает данные для wizard +12. `AI Agent12` → генерирует wizard_plan +13. `Code` → парсит JSON +14. `Code4` → форматирует для Redis +15. **`claimsave_primary`** → **СОХРАНЯЕТ ПЕРВИЧНЫЙ ЧЕРНОВИК** ⭐ +16. `push_wizard1` → пушит wizard_plan в Redis для SSE + +## Что сохраняется в первичный черновик + +- ✅ `wizard_plan` - план вопросов от AI Agent12 +- ✅ `problem_description` - описание проблемы от пользователя +- ✅ `answers_prefill` - предзаполненные ответы (если есть) +- ✅ `coverage_report` - отчёт о покрытии (если есть) +- ✅ `ai_agent1_facts` - данные из AI Agent1 (facts_short, facts_full, problem) +- ✅ `ai_agent13_rag` - RAG ответ от AI Agent13 +- ✅ `session_token` - сессия пользователя (используется для связи, claim_id генерируется позже) +- ✅ `unified_id` - если есть (передается с фронта) +- ✅ `phone`, `email` - контакты пользователя +- ✅ `status_code = 'draft'` - статус черновика +- ⚠️ `claim_id` - пока NULL, будет сгенерирован позже + +## Что НЕ сохраняется на этом этапе + +- ❌ `wizard_answers` - ещё нет (пользователь не ответил) +- ❌ `documents_meta` - ещё нет (файлы не загружены) + +## Данные из AI Agent1 и AI Agent13 + +Эти данные используются в `AI Agent12` для генерации `wizard_plan`, но **также сохраняются в черновик** для дальнейшего использования: + +- **AI Agent1** → `output` (факты полный и короткий): + - `facts_short` - краткая суть проблемы + - `facts_full` - полный текст/саммари + - `problem` - классификатор проблемы + - Сохраняется в `payload.ai_agent1_facts` + +- **AI Agent13** → `output` (RAG ответ): + - Аналитическая справка/правовой ответ из базы знаний + - Сохраняется в `payload.ai_agent13_rag` + +Они передаются в `AI Agent12` через `Edit Fields11`: +- `chatInput` = описание проблемы +- `output` = RAG ответ от AI Agent13 +- `questions_numbered_html` = шаги формы из Google Sheets + +**Важно:** Сохраняем и промежуточные данные (AI Agent1, AI Agent13), и результат (`wizard_plan`), т.к. они могут пригодиться для дальнейшей обработки. + diff --git a/ticket_form/docs/CODE4_FIXED.js b/ticket_form/docs/CODE4_FIXED.js new file mode 100644 index 00000000..ac22295b --- /dev/null +++ b/ticket_form/docs/CODE4_FIXED.js @@ -0,0 +1,77 @@ +// n8n Code node (Run Once) — prepare object for Redis +const items = $input.all(); + +// 1) Найти первый подходящий элемент с parsed.obj +let main = null; +for (const it of items) { + const j = it.json; + if (!j) continue; + // возможные места + if (j.parsed && j.parsed.obj) { main = j.parsed.obj; break; } + if (j.parsed && j.parsed.ok && j.parsed.obj) { main = j.parsed.obj; break; } + if (j.output) { + // если output — строка JSON + try { + const parsed = JSON.parse(j.output); + if (parsed && parsed.wizard_plan) { main = parsed; break; } + } catch (e) {} + } + if (j.json && j.json.wizard_plan) { main = j.json; break; } +} +if (!main) { + // последний шанс: взять items[0].json + main = items[0] ? (items[0].json || items[0]) : null; +} +if (!main) { + throw new Error('Не удалось найти parsed.obj в входных данных'); +} + +// 2) Гарантии структуры +main.wizard_plan = main.wizard_plan || {}; +main.coverage_report = main.coverage_report || {}; +main.coverage_report.docs_received = main.coverage_report.docs_received || []; +main.wizard_plan.risks = main.wizard_plan.risks || ['DOCS_STATUS_UNKNOWN','EXPECTATION_UNSET']; +main.wizard_plan.deadlines = main.wizard_plan.deadlines || [ + { type: 'USER_UPLOAD_TTL', duration_hours: 48 }, + { type: 'USER_APPROVAL_TTL', duration_hours: 24 } +]; + +// 3) Добавить примерный документ (state/cities) — если ещё нет такого id +const exampleId = 'example_state_cities_json'; +const already = main.coverage_report.docs_received.find(d => d.id === exampleId); +if (!already) { + const exampleDoc = { + id: exampleId, + name: 'state_cities_example.json', + type: 'application/json', + uploaded_at: new Date().toISOString(), + content: { + state: 'California', + cities: ['Los Angeles', 'San Francisco', 'San Diego'] + } + }; + main.coverage_report.docs_received.push(exampleDoc); +} + +// 4) session token / key +// Получаем session_token из разных источников (приоритет: Edit Fields11 > Redis Trigger) +const sessionToken = $('Edit Fields11').first().json.session_token + || $('Redis Trigger').first().json.message.session_id + || null; + +// Если session_token недоступен, генерируем временный ключ +if (!sessionToken) { + console.warn('⚠️ session_token не найден, используем временный ключ'); +} + +// Используем session_token для Redis ключа (claim_id будет сгенерирован позже) +const redisKey = `ocr_events:${sessionToken || 'temp-' + Date.now()}`; + +// 5) Возвращаем объект для следующего Redis node +return [{ + json: { + redis_key: redisKey, + redis_value: main + } +}]; + diff --git a/ticket_form/docs/CODE_CREATE_WEB_CONTACT_FINAL.js b/ticket_form/docs/CODE_CREATE_WEB_CONTACT_FINAL.js new file mode 100644 index 00000000..b7c22a94 --- /dev/null +++ b/ticket_form/docs/CODE_CREATE_WEB_CONTACT_FINAL.js @@ -0,0 +1,41 @@ +// Парсим результат CreateWebContact +const rawResult = $node["CreateWebContact"].json.result; + +const contactData = JSON.parse(rawResult); // {"contact_id": "396625", "is_new": false} + +const phone = $('Edit Fields').first().json.phone; + +// Получаем session_id +const session_id = $('Edit Fields').first().json.session_id; + +// Получаем unified_id из ноды user_get +const unified_id = $('user_get').first().json.unified_id || null; + +// Формируем session для Redis (БЕЗ claim_id, с unified_id) +const sessionData = { + // claim_id убран - используем только session_id на этих этапах + unified_id: unified_id, // ← unified_id из PostgreSQL (получаем от user_get) + contact_id: contactData.contact_id, // ← распарсенный ID из CreateWebContact + phone: phone, + is_new_contact: contactData.is_new, // ← флаг нового контакта + status: "draft", + current_step: 1, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + documents: {}, + email: null, + bank_name: null +}; + +return { + session: session_id, + session_id: session_id, // Добавляем для совместимости + unified_id: unified_id, // ✅ Добавляем unified_id в return + contact_id: contactData.contact_id, + is_new_contact: contactData.is_new, + phone: phone, + redis_key: `session:${session_id}`, // ✅ Используем session_id для ключа Redis + redis_value: JSON.stringify(sessionData), + ttl: 604800 +}; + diff --git a/ticket_form/docs/CODE_CREATE_WEB_CONTACT_FIXED.js b/ticket_form/docs/CODE_CREATE_WEB_CONTACT_FIXED.js new file mode 100644 index 00000000..32ad8db4 --- /dev/null +++ b/ticket_form/docs/CODE_CREATE_WEB_CONTACT_FIXED.js @@ -0,0 +1,44 @@ +// Парсим результат CreateWebContact +const rawResult = $node["CreateWebContact"].json.result; + +const contactData = JSON.parse(rawResult); // {"contact_id": "396625", "is_new": false} + +const phone = $('Edit Fields').first().json.phone; + +// Получаем session_id +const session_id = $('Edit Fields').first().json.session_id; + +// Генерируем claim_id +const date = new Date().toISOString().split('T')[0]; +const randomId = Math.random().toString(36).substr(2, 6).toUpperCase(); +const claim_id = `CLM-${date}-${randomId}`; + +// Формируем session для Redis +const sessionData = { + claim_id: claim_id, + contact_id: contactData.contact_id, // ← распарсенный ID + phone: phone, + is_new_contact: contactData.is_new, // ← флаг нового контакта + status: "draft", + current_step: 1, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + voucher: null, + event_type: null, + documents: {}, + email: null, + bank_name: null +}; + +return { + session: session_id, + session_id: session_id, // Добавляем для совместимости + claim_id: claim_id, + contact_id: contactData.contact_id, + is_new_contact: contactData.is_new, + phone: phone, + redis_key: `session:${session_id}`, // ✅ Исправлено: используем session_id вместо session + redis_value: JSON.stringify(sessionData), + ttl: 604800 +}; + diff --git a/ticket_form/frontend/src/components/form/Step1Phone.tsx b/ticket_form/frontend/src/components/form/Step1Phone.tsx index 94de702d..c0d66bbf 100644 --- a/ticket_form/frontend/src/components/form/Step1Phone.tsx +++ b/ticket_form/frontend/src/components/form/Step1Phone.tsx @@ -135,7 +135,7 @@ export default function Step1Phone({ smsCode: code, contact_id: result.contact_id, unified_id: result.unified_id, // ✅ Unified ID из PostgreSQL (получаем от n8n) - claim_id: result.claim_id, + // claim_id убран - используем только session_id на этих этапах is_new_contact: result.is_new_contact }; diff --git a/ticket_form/frontend/src/components/form/StepDescription.tsx b/ticket_form/frontend/src/components/form/StepDescription.tsx index 18bc41d4..cedea8b9 100644 --- a/ticket_form/frontend/src/components/form/StepDescription.tsx +++ b/ticket_form/frontend/src/components/form/StepDescription.tsx @@ -53,20 +53,14 @@ export default function StepDescription({ message.error('Не найден session_id. Попробуйте обновить страницу.'); return; } - if (!formData.claim_id) { - message.error('Не удалось определить номер обращения. Вернитесь на шаг с телефоном.'); - return; - } setSubmitting(true); if (useMockWizard && wizardPlanSample?.wizard_plan) { const mockPrefill = buildPrefillMap(wizardPlanSample.answers_prefill); - const mockClaimId = wizardPlanSample.claim_id || formData.claim_id; updateFormData({ problemDescription: safeDescription, - claim_id: mockClaimId, wizardPlan: wizardPlanSample.wizard_plan, wizardPlanStatus: 'ready', wizardPrefill: mockPrefill, @@ -85,7 +79,6 @@ export default function StepDescription({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: formData.session_id, - claim_id: formData.claim_id, phone: formData.phone, email: formData.email, problem_description: safeDescription, diff --git a/ticket_form/frontend/src/components/form/StepWizardPlan.tsx b/ticket_form/frontend/src/components/form/StepWizardPlan.tsx index 7b7492e3..439e8239 100644 --- a/ticket_form/frontend/src/components/form/StepWizardPlan.tsx +++ b/ticket_form/frontend/src/components/form/StepWizardPlan.tsx @@ -339,19 +339,19 @@ export default function StepWizardPlan({ }, [formValues, plan, questions, documentGroups, questionFileBlocks, handleDocumentBlocksChange, skippedDocuments]); useEffect(() => { - if (!isWaiting || !formData.claim_id || plan) { + if (!isWaiting || !formData.session_id || plan) { return; } - const claimId = formData.claim_id; - const source = new EventSource(`/events/${claimId}`); + const sessionId = formData.session_id; + const source = new EventSource(`/events/${sessionId}`); eventSourceRef.current = source; - debugLoggerRef.current?.('wizard', 'info', '🔌 Подключаемся к SSE для плана вопросов', { claim_id: claimId }); + debugLoggerRef.current?.('wizard', 'info', '🔌 Подключаемся к SSE для плана вопросов', { session_id: sessionId }); // Таймаут: если план не пришёл за 2 минуты (RAG может работать долго), показываем ошибку timeoutRef.current = setTimeout(() => { setConnectionError('План вопросов не получен. Проверьте, что n8n обработал описание проблемы.'); - debugLoggerRef.current?.('wizard', 'error', '⏱️ Таймаут ожидания плана вопросов (2 минуты)', { claim_id: claimId }); + debugLoggerRef.current?.('wizard', 'error', '⏱️ Таймаут ожидания плана вопросов (2 минуты)', { session_id: sessionId }); if (eventSourceRef.current) { eventSourceRef.current.close(); eventSourceRef.current = null; @@ -360,7 +360,7 @@ export default function StepWizardPlan({ source.onopen = () => { setConnectionError(null); - debugLoggerRef.current?.('wizard', 'info', '✅ SSE соединение открыто', { claim_id: claimId }); + debugLoggerRef.current?.('wizard', 'info', '✅ SSE соединение открыто', { session_id: sessionId }); }; source.onerror = (error) => { @@ -368,7 +368,7 @@ export default function StepWizardPlan({ setConnectionError('Не удалось получить ответ от AI. Попробуйте ещё раз.'); source.close(); eventSourceRef.current = null; - debugLoggerRef.current?.('wizard', 'error', '❌ SSE ошибка (wizard)', { claim_id: claimId }); + debugLoggerRef.current?.('wizard', 'error', '❌ SSE ошибка (wizard)', { session_id: sessionId }); }; const extractWizardPayload = (incoming: any): any => { @@ -403,7 +403,7 @@ export default function StepWizardPlan({ // Логируем все события для отладки debugLoggerRef.current?.('wizard', 'info', '📨 Получено SSE событие', { - claim_id: claimId, + session_id: sessionId, event_type: eventType, has_wizard_plan: Boolean(extractWizardPayload(payload)), payload_keys: Object.keys(payload), @@ -419,7 +419,7 @@ export default function StepWizardPlan({ const coverageReport = wizardPayload?.coverage_report; debugLoggerRef.current?.('wizard', 'success', '✨ Получен план вопросов', { - claim_id: claimId, + session_id: sessionId, questions: wizardPlan?.questions?.length || 0, }); @@ -459,11 +459,11 @@ export default function StepWizardPlan({ eventSourceRef.current = null; } }; - }, [isWaiting, formData.claim_id, plan, updateFormData]); + }, [isWaiting, formData.session_id, plan, updateFormData]); const handleRefreshPlan = () => { - if (!formData.claim_id) { - message.error('Не найден claim_id для подписки на события.'); + if (!formData.session_id) { + message.error('Не найден session_id для подписки на события.'); return; } setIsWaiting(true); @@ -561,7 +561,7 @@ export default function StepWizardPlan({ try { setSubmitting(true); addDebugEvent?.('wizard', 'info', '📤 Отправляем данные визарда в n8n', { - claim_id: formData.claim_id, + session_id: formData.session_id, }); const formPayload = new FormData(); @@ -570,7 +570,7 @@ export default function StepWizardPlan({ if (formData.session_id) formPayload.append('session_id', formData.session_id); if (formData.clientIp) formPayload.append('client_ip', formData.clientIp); if (formData.smsCode) formPayload.append('sms_code', formData.smsCode); - if (formData.claim_id) formPayload.append('claim_id', formData.claim_id); + // claim_id убран - используем только session_id на этих этапах if (formData.contact_id) formPayload.append('contact_id', String(formData.contact_id)); if (formData.project_id) formPayload.append('project_id', String(formData.project_id)); if (typeof formData.is_new_contact !== 'undefined') { @@ -1096,12 +1096,12 @@ export default function StepWizardPlan({ ); - if (!formData.claim_id) { + if (!formData.session_id) { return ( Вернуться} /> );