Files
aiform_dev/docs/SQL_CLAIMSAVE_PRIMARY_DRAFT.sql
AI Assistant 0978e485dc feat: Add claim plan confirmation flow via Redis SSE
Problem:
- After wizard form submission, need to wait for claim data from n8n
- Claim data comes via Redis channel claim:plan:{session_token}
- Need to display confirmation form with claim data

Solution:
1. Backend: Added SSE endpoint /api/v1/claim-plan/{session_token}
   - Subscribes to Redis channel claim:plan:{session_token}
   - Streams claim data from n8n to frontend
   - Handles timeouts and errors gracefully

2. Frontend: Added subscription to claim:plan channel
   - StepWizardPlan: After form submission, subscribes to SSE
   - Waits for claim_plan_ready event
   - Shows loading message while waiting
   - On success: saves claimPlanData and shows confirmation step

3. New component: StepClaimConfirmation
   - Displays claim confirmation form in iframe
   - Receives claimPlanData from parent
   - Generates HTML form (placeholder - should call n8n for real HTML)
   - Handles confirmation/cancellation via postMessage

4. ClaimForm: Added conditional step for confirmation
   - Shows StepClaimConfirmation when showClaimConfirmation=true
   - Step appears after StepWizardPlan
   - Only visible when claimPlanData is available

Flow:
1. User fills wizard form → submits
2. Form data sent to n8n via /api/v1/claims/wizard
3. Frontend subscribes to SSE /api/v1/claim-plan/{session_token}
4. n8n processes data → publishes to Redis claim:plan:{session_token}
5. Backend receives → streams to frontend via SSE
6. Frontend receives → shows StepClaimConfirmation
7. User confirms → proceeds to next step

Files:
- backend/app/api/events.py: Added stream_claim_plan endpoint
- frontend/src/components/form/StepWizardPlan.tsx: Added subscribeToClaimPlan
- frontend/src/components/form/StepClaimConfirmation.tsx: New component
- frontend/src/pages/ClaimForm.tsx: Added confirmation step to steps array
2025-11-24 13:36:14 +03:00

219 lines
7.3 KiB
SQL
Raw Permalink 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.

-- ============================================================================
-- SQL запрос для n8n: Сохранение первичного черновика заявки
-- ============================================================================
-- Назначение: Сохранить первичный черновик сразу после генерации wizard_plan
-- Использует session_token для связи (claim_id генерируется позже)
--
-- Параметры:
-- $1 = payload_json (jsonb) - полный payload с wizard_plan, problem_description,
-- AI Agent1, AI Agent13 и т.д.
-- $2 = session_token (text) - сессия пользователя
-- $3 = unified_id (text, опционально) - unified_id пользователя
--
-- Возвращает:
-- claim - объект с claim_id (UUID), session_token, status_code, payload
--
-- Использование в n8n:
-- 1. PostgreSQL node
-- 2. Query Type: Execute Query
-- 3. Parameters:
-- $1 = {{ JSON.stringify({...}) }}
-- $2 = {{ $('Edit Fields11').first().json.session_token }}
-- $3 = {{ $('Edit Fields10').first().json.unified_id || null }}
-- ============================================================================
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(
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
),
'{answers_prefill}',
COALESCE(
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,
c.payload->'answers_prefill',
'[]'::jsonb
),
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', COALESCE(u.id::text, cf.claim_uuid::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;