346 lines
13 KiB
MySQL
346 lines
13 KiB
MySQL
|
|
-- ============================================================================
|
|||
|
|
-- 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()
|
|||
|
|
-- }
|
|||
|
|
-- }
|
|||
|
|
-- };
|
|||
|
|
-- ============================================================================
|
|||
|
|
|
|||
|
|
|