# Исправление узла `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`), т.к. они могут пригодиться для дальнейшей обработки.