Files
aiform_prod/docs/SQL_SAVE_DRAFT_NEW_FLOW.sql
AI Assistant 02689e65db fix: Исправление загрузки документов и SQL запросов
- Исправлена потеря документов при обновлении черновика (SQL объединяет вместо перезаписи)
- Исправлено определение типа документа (приоритет field_label над field_name)
- Исправлены дубликаты в documents_meta и documents_uploaded
- Добавлена передача group_index с фронтенда для правильного field_name
- Исправлены все документы в таблице clpr_claim_documents с правильными field_name
- Обновлены SQL запросы: claimsave и claimsave_final для нового флоу
- Добавлена поддержка multi-file upload для одного документа
- Исправлены дубликаты в списке загруженных документов на фронтенде

Файлы:
- SQL: SQL_CLAIMSAVE_FIXED_NEW_FLOW.sql, SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED.sql
- n8n: N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js (поддержка group_index)
- Backend: documents.py (передача group_index в n8n)
- Frontend: StepWizardPlan.tsx (передача group_index, исправление дубликатов)
- Скрипты: fix_claim_documents_field_names.py, fix_documents_meta_duplicates.py

Результат: документы больше не теряются, имеют правильные типы и field_name
2025-11-26 19:54:51 +03:00

346 lines
13 KiB
SQL
Raw 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: Сохранение черновика (НОВЫЙ ФЛОУ с документами)
-- ============================================================================
-- Назначение: Сохранить черновик сразу после анализа описания проблемы
-- AI Agent возвращает facts + docs (список документов)
--
-- Вход от AI Agent:
-- output: { facts_short, facts_full, problem, recommendation, docs: [...] }
-- propertyName: { session_id, phone, unified_id, contact_id, ФИО и т.д. }
--
-- Параметры:
-- $1 = payload_json (jsonb) - полный payload с output и propertyName
-- $2 = session_token (text) - сессия пользователя (из propertyName.session_id)
-- $3 = unified_id (text) - unified_id пользователя
-- $4 = problem_description (text) - исходное описание проблемы от пользователя
--
-- Возвращает:
-- claim - объект с claim_id, session_token, status_code, documents_required
-- ============================================================================
WITH input_data AS (
SELECT
$1::jsonb AS payload,
$2::text AS session_token_str,
NULLIF($3::text, '') AS unified_id_str,
NULLIF($4::text, '') AS problem_desc
),
-- Извлекаем данные из payload
parsed_data AS (
SELECT
input_data.*,
input_data.payload->'output' AS ai_output,
input_data.payload->'propertyName' AS user_data,
input_data.payload->'output'->'docs' AS documents_required
FROM input_data
),
-- Проверяем существующий черновик по session_token
existing_claim AS (
SELECT id, payload
FROM clpr_claims
WHERE session_token = (SELECT session_token_str FROM input_data)
LIMIT 1
),
-- Генерируем или используем существующий UUID
claim_id_resolved AS (
SELECT
COALESCE(
(SELECT id FROM existing_claim),
gen_random_uuid()
) AS claim_uuid
),
-- INSERT или UPDATE черновика
upserted_claim AS (
INSERT INTO clpr_claims (
id,
session_token,
unified_id,
channel,
type_code,
status_code,
payload,
created_at,
updated_at,
expires_at
)
SELECT
claim_id_resolved.claim_uuid,
parsed_data.session_token_str,
COALESCE(parsed_data.unified_id_str, parsed_data.user_data->>'unified_id'),
'web_form',
'consumer',
'draft_new', -- ✅ Новый статус: только описание + документы
jsonb_build_object(
'claim_id', claim_id_resolved.claim_uuid::text,
'problem_description', COALESCE(parsed_data.problem_desc, parsed_data.user_data->>'problem_description'),
-- AI анализ
'ai_analysis', jsonb_build_object(
'facts_short', parsed_data.ai_output->>'facts_short',
'facts_full', parsed_data.ai_output->>'facts_full',
'problem', parsed_data.ai_output->>'problem',
'recommendation', parsed_data.ai_output->>'recommendation'
),
-- ✅ Список необходимых документов (новое!)
'documents_required', COALESCE(parsed_data.documents_required, '[]'::jsonb),
'documents_uploaded', '[]'::jsonb,
'documents_skipped', '[]'::jsonb,
'current_doc_index', 0,
-- Данные пользователя
'phone', COALESCE(parsed_data.user_data->>'phone', ''),
'email', COALESCE(parsed_data.user_data->>'email', ''),
'contact_id', parsed_data.user_data->>'contact_id',
-- ФИО и паспортные данные (для заявления)
'applicant', jsonb_build_object(
'lastname', parsed_data.user_data->>'lastname',
'firstname', parsed_data.user_data->>'firstname',
'middle_name', parsed_data.user_data->>'middle_name',
'birthday', parsed_data.user_data->>'birthday',
'birthplace', parsed_data.user_data->>'birthplace',
'inn', parsed_data.user_data->>'inn',
'address', parsed_data.user_data->>'mailingstreet',
'zip', parsed_data.user_data->>'mailingzip'
),
-- Telegram ID если есть
'tg_id', parsed_data.user_data->>'tg_id',
-- Флаги готовности
'wizard_ready', false,
'claim_ready', false
),
now(),
now(),
now() + interval '14 days'
FROM parsed_data, claim_id_resolved
ON CONFLICT (id) DO UPDATE SET
unified_id = COALESCE(EXCLUDED.unified_id, clpr_claims.unified_id),
status_code = 'draft_new',
payload = clpr_claims.payload || EXCLUDED.payload,
updated_at = now(),
expires_at = now() + interval '14 days'
RETURNING id, session_token, status_code, payload
)
-- Возвращаем результат для n8n
SELECT
jsonb_build_object(
'claim_id', upserted_claim.id::text,
'session_token', upserted_claim.session_token,
'status_code', upserted_claim.status_code,
'documents_required', upserted_claim.payload->'documents_required',
'documents_count', jsonb_array_length(COALESCE(upserted_claim.payload->'documents_required', '[]'::jsonb))
) AS claim
FROM upserted_claim;
-- ============================================================================
-- Пример вызова в n8n (PostgreSQL Node):
-- ============================================================================
--
-- Параметры:
-- $1 = {{ JSON.stringify($json) }} -- Весь payload от AI Agent
-- $2 = {{ $json.propertyName.session_id }} -- session_token
-- $3 = {{ $json.propertyName.unified_id }} -- unified_id
-- $4 = {{ $node["Redis Trigger"].json.description }} -- Исходное описание проблемы
--
-- После выполнения SQL, в Code Node пушим в Redis:
--
-- const result = $input.first().json.claim;
--
-- return {
-- json: {
-- channel: `ocr_events:${result.session_token}`,
-- event: {
-- event_type: 'documents_list_ready',
-- claim_id: result.claim_id,
-- session_id: result.session_token,
-- documents_required: result.documents_required,
-- documents_count: result.documents_count,
-- timestamp: new Date().toISOString()
-- }
-- }
-- };
-- ============================================================================
-- SQL запрос для n8n: Сохранение черновика (НОВЫЙ ФЛОУ с документами)
-- ============================================================================
-- Назначение: Сохранить черновик сразу после анализа описания проблемы
-- AI Agent возвращает facts + docs (список документов)
--
-- Вход от AI Agent:
-- output: { facts_short, facts_full, problem, recommendation, docs: [...] }
-- propertyName: { session_id, phone, unified_id, contact_id, ФИО и т.д. }
--
-- Параметры:
-- $1 = payload_json (jsonb) - полный payload с output и propertyName
-- $2 = session_token (text) - сессия пользователя (из propertyName.session_id)
-- $3 = unified_id (text) - unified_id пользователя
-- $4 = problem_description (text) - исходное описание проблемы от пользователя
--
-- Возвращает:
-- claim - объект с claim_id, session_token, status_code, documents_required
-- ============================================================================
WITH input_data AS (
SELECT
$1::jsonb AS payload,
$2::text AS session_token_str,
NULLIF($3::text, '') AS unified_id_str,
NULLIF($4::text, '') AS problem_desc
),
-- Извлекаем данные из payload
parsed_data AS (
SELECT
input_data.*,
input_data.payload->'output' AS ai_output,
input_data.payload->'propertyName' AS user_data,
input_data.payload->'output'->'docs' AS documents_required
FROM input_data
),
-- Проверяем существующий черновик по session_token
existing_claim AS (
SELECT id, payload
FROM clpr_claims
WHERE session_token = (SELECT session_token_str FROM input_data)
LIMIT 1
),
-- Генерируем или используем существующий UUID
claim_id_resolved AS (
SELECT
COALESCE(
(SELECT id FROM existing_claim),
gen_random_uuid()
) AS claim_uuid
),
-- INSERT или UPDATE черновика
upserted_claim AS (
INSERT INTO clpr_claims (
id,
session_token,
unified_id,
channel,
type_code,
status_code,
payload,
created_at,
updated_at,
expires_at
)
SELECT
claim_id_resolved.claim_uuid,
parsed_data.session_token_str,
COALESCE(parsed_data.unified_id_str, parsed_data.user_data->>'unified_id'),
'web_form',
'consumer',
'draft_new', -- ✅ Новый статус: только описание + документы
jsonb_build_object(
'claim_id', claim_id_resolved.claim_uuid::text,
'problem_description', COALESCE(parsed_data.problem_desc, parsed_data.user_data->>'problem_description'),
-- AI анализ
'ai_analysis', jsonb_build_object(
'facts_short', parsed_data.ai_output->>'facts_short',
'facts_full', parsed_data.ai_output->>'facts_full',
'problem', parsed_data.ai_output->>'problem',
'recommendation', parsed_data.ai_output->>'recommendation'
),
-- ✅ Список необходимых документов (новое!)
'documents_required', COALESCE(parsed_data.documents_required, '[]'::jsonb),
'documents_uploaded', '[]'::jsonb,
'documents_skipped', '[]'::jsonb,
'current_doc_index', 0,
-- Данные пользователя
'phone', COALESCE(parsed_data.user_data->>'phone', ''),
'email', COALESCE(parsed_data.user_data->>'email', ''),
'contact_id', parsed_data.user_data->>'contact_id',
-- ФИО и паспортные данные (для заявления)
'applicant', jsonb_build_object(
'lastname', parsed_data.user_data->>'lastname',
'firstname', parsed_data.user_data->>'firstname',
'middle_name', parsed_data.user_data->>'middle_name',
'birthday', parsed_data.user_data->>'birthday',
'birthplace', parsed_data.user_data->>'birthplace',
'inn', parsed_data.user_data->>'inn',
'address', parsed_data.user_data->>'mailingstreet',
'zip', parsed_data.user_data->>'mailingzip'
),
-- Telegram ID если есть
'tg_id', parsed_data.user_data->>'tg_id',
-- Флаги готовности
'wizard_ready', false,
'claim_ready', false
),
now(),
now(),
now() + interval '14 days'
FROM parsed_data, claim_id_resolved
ON CONFLICT (id) DO UPDATE SET
unified_id = COALESCE(EXCLUDED.unified_id, clpr_claims.unified_id),
status_code = 'draft_new',
payload = clpr_claims.payload || EXCLUDED.payload,
updated_at = now(),
expires_at = now() + interval '14 days'
RETURNING id, session_token, status_code, payload
)
-- Возвращаем результат для n8n
SELECT
jsonb_build_object(
'claim_id', upserted_claim.id::text,
'session_token', upserted_claim.session_token,
'status_code', upserted_claim.status_code,
'documents_required', upserted_claim.payload->'documents_required',
'documents_count', jsonb_array_length(COALESCE(upserted_claim.payload->'documents_required', '[]'::jsonb))
) AS claim
FROM upserted_claim;
-- ============================================================================
-- Пример вызова в n8n (PostgreSQL Node):
-- ============================================================================
--
-- Параметры:
-- $1 = {{ JSON.stringify($json) }} -- Весь payload от AI Agent
-- $2 = {{ $json.propertyName.session_id }} -- session_token
-- $3 = {{ $json.propertyName.unified_id }} -- unified_id
-- $4 = {{ $node["Redis Trigger"].json.description }} -- Исходное описание проблемы
--
-- После выполнения SQL, в Code Node пушим в Redis:
--
-- const result = $input.first().json.claim;
--
-- return {
-- json: {
-- channel: `ocr_events:${result.session_token}`,
-- event: {
-- event_type: 'documents_list_ready',
-- claim_id: result.claim_id,
-- session_id: result.session_token,
-- documents_required: result.documents_required,
-- documents_count: result.documents_count,
-- timestamp: new Date().toISOString()
-- }
-- }
-- };
-- ============================================================================