fix: Preserve wizard_plan and AI fields from DB when updating claim
Problem: - When uploading files on Step 3, wizard_plan was reset to NULL - wizard_plan is created on Step 2 (StepDescription) and saved by claimsave_primary - form_get workflow on Step 3 doesn't receive wizard_plan again - Old SQL was overwriting wizard_plan with NULL Solution: - Add 'existing_claim' CTE to read current payload from DB - Modified all parsers to fallback to DB values if field not in incoming payload: * wizard_plan_parsed - preserves generated wizard plan * answers_prefill_parsed - preserves AI-generated prefill * coverage_report_parsed - preserves coverage analysis * ai_agent1_facts_parsed - preserves fact extraction * ai_agent13_rag_parsed - preserves RAG analysis * problem_description_parsed - preserves user description Flow: 1. Step 2: User describes problem → claimsave_primary saves wizard_plan ✅ 2. Step 3: User uploads files → form_get/claimsave preserves wizard_plan from DB ✅ File: docs/SQL_CLAIMSAVE_UPSERT_SIMPLE.sql Next: Update n8n workflow 'form_get' node 'claimsave' with this SQL
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
-- Упрощённый UPSERT для сохранения claim с известным claim_id
|
||||
-- Используется в n8n workflow: form_get (нода claimsave)
|
||||
-- Дата: 2025-11-21
|
||||
-- Дата: 2025-11-21 (обновлено: сохранение существующих полей)
|
||||
-- Описание: Простой INSERT/UPDATE для claim, т.к. claim_id уже известен
|
||||
-- ВАЖНО: Сохраняет wizard_plan и другие поля из БД, если не пришли новые
|
||||
|
||||
-- Входные параметры:
|
||||
-- $1: payload_partial_json (jsonb) - данные формы с wizard_answers, wizard_plan, documents_meta
|
||||
@@ -13,6 +14,16 @@ WITH partial AS (
|
||||
$2::text AS claim_id_str
|
||||
),
|
||||
|
||||
-- Получаем существующий payload из БД (если запись есть)
|
||||
existing_claim AS (
|
||||
SELECT
|
||||
id,
|
||||
payload
|
||||
FROM clpr_claims
|
||||
WHERE id = (SELECT claim_id_str::uuid FROM partial)
|
||||
LIMIT 1
|
||||
),
|
||||
|
||||
-- Парсим wizard_answers
|
||||
wizard_answers_parsed AS (
|
||||
SELECT
|
||||
@@ -27,20 +38,94 @@ wizard_answers_parsed AS (
|
||||
FROM partial
|
||||
),
|
||||
|
||||
-- Парсим wizard_plan
|
||||
-- Парсим wizard_plan (или берём из существующей записи)
|
||||
wizard_plan_parsed AS (
|
||||
SELECT
|
||||
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'
|
||||
-- Если нет в payload - берём из существующей записи в БД
|
||||
WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'wizard_plan' IS NOT NULL)
|
||||
THEN (SELECT payload->'wizard_plan' FROM existing_claim)
|
||||
ELSE NULL
|
||||
END AS wizard_plan
|
||||
FROM partial
|
||||
),
|
||||
|
||||
-- Парсим answers_prefill (или берём из БД)
|
||||
answers_prefill_parsed AS (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN partial.p->'answers_prefill' IS NOT NULL
|
||||
AND jsonb_typeof(partial.p->'answers_prefill') = 'array'
|
||||
THEN partial.p->'answers_prefill'
|
||||
WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'answers_prefill' IS NOT NULL)
|
||||
THEN (SELECT payload->'answers_prefill' FROM existing_claim)
|
||||
ELSE '[]'::jsonb
|
||||
END AS answers_prefill
|
||||
FROM partial
|
||||
),
|
||||
|
||||
-- Парсим coverage_report (или берём из БД)
|
||||
coverage_report_parsed AS (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN partial.p->'coverage_report' IS NOT NULL
|
||||
AND jsonb_typeof(partial.p->'coverage_report') = 'object'
|
||||
THEN partial.p->'coverage_report'
|
||||
WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'coverage_report' IS NOT NULL)
|
||||
THEN (SELECT payload->'coverage_report' FROM existing_claim)
|
||||
ELSE NULL
|
||||
END AS coverage_report
|
||||
FROM partial
|
||||
),
|
||||
|
||||
-- Парсим ai_agent1_facts (или берём из БД)
|
||||
ai_agent1_facts_parsed AS (
|
||||
SELECT
|
||||
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'
|
||||
WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'ai_agent1_facts' IS NOT NULL)
|
||||
THEN (SELECT payload->'ai_agent1_facts' FROM existing_claim)
|
||||
ELSE NULL
|
||||
END AS ai_agent1_facts
|
||||
FROM partial
|
||||
),
|
||||
|
||||
-- Парсим ai_agent13_rag (или берём из БД)
|
||||
ai_agent13_rag_parsed AS (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN partial.p->'ai_agent13_rag' IS NOT NULL
|
||||
THEN partial.p->'ai_agent13_rag'
|
||||
WHEN partial.p->>'ai_agent13_rag' IS NOT NULL
|
||||
THEN to_jsonb(partial.p->>'ai_agent13_rag')
|
||||
WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'ai_agent13_rag' IS NOT NULL)
|
||||
THEN (SELECT payload->'ai_agent13_rag' FROM existing_claim)
|
||||
ELSE NULL
|
||||
END AS ai_agent13_rag
|
||||
FROM partial
|
||||
),
|
||||
|
||||
-- Парсим problem_description (или берём из БД)
|
||||
problem_description_parsed AS (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN partial.p->>'problem_description' IS NOT NULL
|
||||
THEN partial.p->>'problem_description'
|
||||
WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->>'problem_description' IS NOT NULL)
|
||||
THEN (SELECT payload->>'problem_description' FROM existing_claim)
|
||||
ELSE NULL
|
||||
END AS problem_description
|
||||
FROM partial
|
||||
),
|
||||
|
||||
-- UPSERT claim
|
||||
claim_upsert AS (
|
||||
INSERT INTO clpr_claims (
|
||||
@@ -72,9 +157,16 @@ claim_upsert AS (
|
||||
END,
|
||||
jsonb_build_object(
|
||||
'claim_id', partial.claim_id_str,
|
||||
'problem_description', (SELECT problem_description FROM problem_description_parsed),
|
||||
'answers', (SELECT answers FROM wizard_answers_parsed),
|
||||
'documents_meta', COALESCE(partial.p->'documents_meta', '[]'::jsonb),
|
||||
'wizard_plan', (SELECT wizard_plan FROM wizard_plan_parsed)
|
||||
'wizard_plan', (SELECT wizard_plan FROM wizard_plan_parsed),
|
||||
'answers_prefill', (SELECT answers_prefill FROM answers_prefill_parsed),
|
||||
'coverage_report', (SELECT coverage_report FROM coverage_report_parsed),
|
||||
'ai_agent1_facts', (SELECT ai_agent1_facts FROM ai_agent1_facts_parsed),
|
||||
'ai_agent13_rag', (SELECT ai_agent13_rag FROM ai_agent13_rag_parsed),
|
||||
'phone', COALESCE(partial.p->>'phone', (SELECT payload->>'phone' FROM existing_claim)),
|
||||
'email', COALESCE(partial.p->>'email', (SELECT payload->>'email' FROM existing_claim))
|
||||
),
|
||||
COALESCE(
|
||||
(SELECT created_at FROM clpr_claims WHERE id = partial.claim_id_str::uuid),
|
||||
@@ -89,14 +181,7 @@ claim_upsert AS (
|
||||
contact_id = COALESCE(EXCLUDED.contact_id, clpr_claims.contact_id),
|
||||
phone = COALESCE(EXCLUDED.phone, clpr_claims.phone),
|
||||
status_code = EXCLUDED.status_code,
|
||||
payload = (
|
||||
-- Сохраняем старые поля, которых нет в новом payload
|
||||
clpr_claims.payload
|
||||
- 'answers'
|
||||
- 'documents_meta'
|
||||
- 'wizard_plan'
|
||||
- 'claim_id'
|
||||
) || EXCLUDED.payload,
|
||||
payload = EXCLUDED.payload,
|
||||
updated_at = now(),
|
||||
expires_at = now() + interval '14 days'
|
||||
RETURNING id, status_code, payload, unified_id, contact_id, phone, session_token
|
||||
@@ -161,9 +246,21 @@ SELECT
|
||||
)) FROM docs_upsert) AS documents;
|
||||
|
||||
/*
|
||||
ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
|
||||
============================================================================
|
||||
КЛЮЧЕВЫЕ ИЗМЕНЕНИЯ (2025-11-21):
|
||||
============================================================================
|
||||
|
||||
1. Вызов с wizard_answers и wizard_plan:
|
||||
1. Добавлена CTE "existing_claim" - читает существующий payload из БД
|
||||
2. Все парсеры (wizard_plan, answers_prefill, coverage_report и т.д.)
|
||||
теперь проверяют БД, если поле не пришло в payload_partial_json
|
||||
3. Это критично для workflow form_get, где wizard_plan создаётся на Step 2,
|
||||
а файлы загружаются на Step 3 (без повторной отправки wizard_plan)
|
||||
|
||||
============================================================================
|
||||
ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
|
||||
============================================================================
|
||||
|
||||
1. Первое сохранение (после Step 2 - описание проблемы):
|
||||
|
||||
SELECT * FROM ... WHERE ... = (
|
||||
'{
|
||||
@@ -171,8 +268,27 @@ SELECT * FROM ... WHERE ... = (
|
||||
"unified_id": "usr_xxx",
|
||||
"contact_id": "12345",
|
||||
"phone": "79262306381",
|
||||
"wizard_answers": "{\\"q1\\": \\"answer1\\"}",
|
||||
"wizard_plan": "{\\"questions\\": [...]}",
|
||||
"problem_description": "Не вернули деньги...",
|
||||
"wizard_plan": {...полный wizard_plan...},
|
||||
"ai_agent1_facts": {...},
|
||||
"ai_agent13_rag": "...",
|
||||
"answers_prefill": [...],
|
||||
"coverage_report": {...}
|
||||
}'::jsonb,
|
||||
'uuid-here'::text
|
||||
);
|
||||
|
||||
Результат: Создаётся claim с wizard_plan ✅
|
||||
|
||||
---
|
||||
|
||||
2. Обновление с файлами (Step 3 - загрузка документов):
|
||||
|
||||
SELECT * FROM ... WHERE ... = (
|
||||
'{
|
||||
"session_id": "sess_xxx",
|
||||
"unified_id": "usr_xxx",
|
||||
"wizard_answers": {"q1": "answer1"},
|
||||
"documents_meta": [
|
||||
{
|
||||
"field_name": "uploads[0][0]",
|
||||
@@ -183,25 +299,35 @@ SELECT * FROM ... WHERE ... = (
|
||||
}
|
||||
]
|
||||
}'::jsonb,
|
||||
'uuid-here'::text
|
||||
'СУЩЕСТВУЮЩИЙ-uuid'::text
|
||||
);
|
||||
|
||||
2. Вызов БЕЗ файлов (только answers):
|
||||
Результат:
|
||||
- Обновляется answers ✅
|
||||
- Добавляются documents_meta ✅
|
||||
- wizard_plan СОХРАНЯЕТСЯ из БД ✅ (не затирается!)
|
||||
|
||||
---
|
||||
|
||||
3. Сохранение БЕЗ файлов (только answers):
|
||||
|
||||
SELECT * FROM ... WHERE ... = (
|
||||
'{
|
||||
"session_id": "sess_xxx",
|
||||
"unified_id": "usr_xxx",
|
||||
"contact_id": "12345",
|
||||
"phone": "79262306381",
|
||||
"wizard_answers": "{\\"q1\\": \\"answer1\\"}",
|
||||
"wizard_plan": null,
|
||||
"wizard_answers": {"q1": "answer1"},
|
||||
"documents_meta": []
|
||||
}'::jsonb,
|
||||
'uuid-here'::text
|
||||
);
|
||||
|
||||
РЕЗУЛЬТАТ:
|
||||
Результат:
|
||||
- status_code = 'draft' (т.к. docs_exist != true)
|
||||
- wizard_plan сохраняется из БД ✅
|
||||
|
||||
============================================================================
|
||||
ОЖИДАЕМЫЙ РЕЗУЛЬТАТ:
|
||||
============================================================================
|
||||
|
||||
{
|
||||
"claim": {
|
||||
"claim_id": "uuid",
|
||||
@@ -211,17 +337,32 @@ SELECT * FROM ... WHERE ... = (
|
||||
"contact_id": "12345",
|
||||
"phone": "79262306381",
|
||||
"session_token": "sess_xxx",
|
||||
"payload": {...}
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"field_name": "uploads[0][0]",
|
||||
"file_id": "clientright/0/file.pdf",
|
||||
"file_name": "file.pdf",
|
||||
"original_file_name": "original.pdf"
|
||||
"payload": {
|
||||
"claim_id": "uuid",
|
||||
"problem_description": "...",
|
||||
"answers": {...},
|
||||
"documents_meta": [...],
|
||||
"wizard_plan": {...}, // ← СОХРАНЯЕТСЯ из БД!
|
||||
"answers_prefill": [...],
|
||||
"coverage_report": {...},
|
||||
"ai_agent1_facts": {...},
|
||||
"ai_agent13_rag": "..."
|
||||
}
|
||||
]
|
||||
},
|
||||
"documents": [...]
|
||||
}
|
||||
*/
|
||||
|
||||
============================================================================
|
||||
TROUBLESHOOTING:
|
||||
============================================================================
|
||||
|
||||
Проблема: wizard_plan всё равно NULL после загрузки файлов
|
||||
Причина: В n8n workflow form_get не передаётся claim_id в payload
|
||||
Решение: Убедиться, что в set_token1 извлекается claim_id из webhook:
|
||||
"claim_id": "={{ $('Webhook').item.json.body.claim_id }}"
|
||||
|
||||
Проблема: Затираются ai_agent1_facts после Step 3
|
||||
Причина: Не включены в payload при отправке
|
||||
Решение: SQL сохраняет их из БД автоматически ✅
|
||||
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user