// Code node (JavaScript). Input: items[0].json = либо объект, либо массив таких объектов, как ты прислал. // Output: по одному нормализованному объекту на кейс. // Никаких внешних зависимостей, всё на ванильном JS. function toNullish(v) { if (v === undefined || v === null) return null; if (typeof v === 'string' && v.trim() === '') return null; return v; } function pick(o, path, def = null) { try { return toNullish(path.split('.').reduce((acc, k) => (acc == null ? undefined : acc[k]), o)); } catch { return def; } } function mapDocuments(docs = []) { // Проверяем, что docs не null и является массивом if (!docs || !Array.isArray(docs)) return []; return docs.map(d => ({ id: toNullish(d.id), claim_document_id: toNullish(d.id), // у тебя id = claim_document_id file_id: toNullish(d.file_id), file_url: toNullish(d.file_url), file_name: toNullish(d.file_name), original_file_name: toNullish(d.original_file_name), field_name: toNullish(d.field_name), upload_description: toNullish(d.upload_description), uploaded_at: toNullish(d.uploaded_at), filename_for_upload: toNullish(d.filename_for_upload), })); } function mapVisionDocs(vds = []) { // Проверяем, что vds не null и является массивом if (!vds || !Array.isArray(vds)) return []; return vds.map(v => ({ claim_document_id: toNullish(v.claim_document_id), vision_document_id: toNullish(v.vision_document_id), pages: toNullish(v.pages), content_sha256: toNullish(v.content_sha256), vision_text: toNullish(v.vision_text), vision_pages: Array.isArray(v.vision_pages) ? v.vision_pages.map(p => ({ page: toNullish(p.page), uid: toNullish(p.uid), })) : null, })); } function mapCombinedDocs(cds = []) { // Проверяем, что cds не null и является массивом if (!cds || !Array.isArray(cds)) return []; return cds.map(c => ({ claim_document_id: toNullish(c.claim_document_id), combined_document_id: toNullish(c.combined_document_id), pages: toNullish(c.pages), content_sha256: toNullish(c.content_sha256), combined_text: toNullish(c.combined_text), page_summaries: Array.isArray(c.page_summaries) ? c.page_summaries.map(ps => ({ page: toNullish(ps.page), chars: toNullish(ps.chars), uid: toNullish(ps.uid), image_url: toNullish(ps.image_url), })) : null, })); } function mapDialogHistory(h = []) { // ИСПРАВЛЕНО: Проверяем, что h не null и является массивом if (!h || !Array.isArray(h)) return []; return h.map(m => ({ id: toNullish(m.id), role: toNullish(m.role), message: toNullish(m.message), message_type: toNullish(m.message_type), tg_message_id: toNullish(m.tg_message_id), created_at: toNullish(m.created_at), })); } function mapCoverageReport(cr = null) { if (!cr) return null; return { questions: Array.isArray(cr.questions) ? cr.questions.map(q => ({ name: toNullish(q.name), value: toNullish(q.value), status: toNullish(q.status), source: toNullish(q.source), confidence: toNullish(q.confidence), })) : null, docs_missing: Array.isArray(cr.docs_missing) ? cr.docs_missing : null, docs_received: Array.isArray(cr.docs_received) ? cr.docs_received : null, }; } function normalizeOne(src) { const claim = src.claim ?? {}; const userInfo = src.user_info ?? {}; const propertyName = claim.propertyName ?? {}; // answers_parsed уже есть в claim; не мудрим — возвращаем как есть, пустоты -> null const answersParsed = claim.answers_parsed ? Object.fromEntries( Object.entries(claim.answers_parsed).map(([k, v]) => [k, toNullish(v)]) ) : null; // wizard план (часто нужен на фронте) — оставим ключевые поля let wizard = null; try { const parsed = typeof claim.wizard_plan === 'string' ? JSON.parse(claim.wizard_plan) : (claim.wizard_plan_parsed ?? null); if (parsed) { wizard = { version: toNullish(parsed.version), case_type: toNullish(parsed.case_type), goals: Array.isArray(parsed.goals) ? parsed.goals : null, documents: Array.isArray(parsed.documents) ? parsed.documents : null, questions: Array.isArray(parsed.questions) ? parsed.questions : null, risks: Array.isArray(parsed.risks) ? parsed.risks : null, deadlines: Array.isArray(parsed.deadlines) ? parsed.deadlines : null, ask_order: Array.isArray(parsed.ask_order) ? parsed.ask_order : null, notes: toNullish(parsed.notes), user_text: toNullish(parsed.user_text), }; } } catch { wizard = null; } // Склеиваем user — берём user_info, плюс propertyName на всякий, и то, что лежит в диалогах const user = { channel: toNullish(userInfo.channel ?? propertyName.channel), user_id: toNullish(userInfo.user_id ?? propertyName.user_id), unified_id: toNullish(userInfo.unified_id ?? propertyName.unified_id), telegram_id: toNullish(userInfo.telegram_id ?? propertyName.telegram_id ?? claim.telegram_id), session_token: toNullish(userInfo.session_token ?? propertyName.session_token ?? claim.session_token), }; // Собираем const out = { case: { id: toNullish(pick(claim, 'id')), prefix: toNullish(pick(claim, 'prefix')), channel: toNullish(pick(claim, 'channel')), type_code: toNullish(pick(claim, 'type_code')), status_code: toNullish(pick(claim, 'status_code')), created_at: toNullish(pick(claim, 'created_at')), updated_at: toNullish(pick(claim, 'updated_at')), telegram_id: toNullish(pick(claim, 'telegram_id')), session_token: toNullish(pick(claim, 'session_token')), unified_id: toNullish(pick(claim, 'unified_id')), case_type: toNullish(pick(claim, 'case_type')), }, user, // см. выше answers: answersParsed, // что загрузили documents: mapDocuments(src.documents), // OCR/Vision/Combined, если есть vision_docs: mapVisionDocs(src.vision_docs), combined_docs: mapCombinedDocs(src.combined_docs), // что там в "coverage_report" (кто что заполнил/не заполнил в мастере) coverage_report: mapCoverageReport(pick(claim, 'coverage_report')), // история чата (ID, роли, тексты) dialog_history: mapDialogHistory(src.dialog_history), // на всякий — куда и что складывали на S3 в момент сохранения s3_manifest: { session_token: toNullish(pick(claim, 'session_token')), documents_meta: Array.isArray(claim.documents_meta) ? claim.documents_meta : null, }, // флаги/риски, что засетили при сохранении risks: Array.isArray(claim.risks) ? claim.risks : null, // план (wizard), как есть — пригодится фронту и валидаторам wizard_plan: wizard, }; return out; } // === entrypoint === const raw = items[0]?.json ?? {}; const arr = Array.isArray(raw) ? raw : [raw]; // опциональный фильтр по claim_id, если в item передадут { claim_id: "..." } const claimIdFilter = items[0]?.json?.claim_id || items[0]?.json?.claimId || null; // Прогоняем всё, отдаём по одному Item на кейс const results = arr .map(normalizeOne) .filter(obj => (claimIdFilter ? obj.case.id === claimIdFilter : true)) .map(obj => ({ json: obj })); return results.length ? results : [{ json: null }];