{"workflow":{"id":14319,"name":"Convert CSV/XLSX files into a normalized SQL schema with GPT-4","views":50,"recentViews":2,"totalViews":50,"createdAt":"2026-03-25T15:03:14.918Z","description":"\nAutomatically converts CSV/XLSX files into a fully validated database schema using AI, generating SQL scripts, ERD diagrams, a data dictionary, and load plans to accelerate database design and data onboarding.\n\n---\n\n## EXPLANATION\n\nThis workflow automates the end-to-end process of transforming raw CSV or Excel data into a production-ready relational database schema.\n\nIt begins by accepting file uploads through a webhook, detecting file type, and extracting structured data. The workflow performs data cleaning and deep profiling to analyze column types, uniqueness, null values, and patterns. A column analysis engine identifies candidate primary keys and potential relationships.\n\nAn AI agent then generates a normalized schema by organizing data into tables, assigning appropriate SQL data types, and defining primary and foreign keys. The schema is validated using rule-based checks to ensure data integrity, correct relationships, and proper normalization.\n\nIf validation fails, the workflow automatically refines the schema through a revision loop. Once validated, it generates SQL DDL scripts, ERD diagrams, a data dictionary, and a load plan that determines the correct order for inserting data.\n\nFinally, all outputs are combined and returned via webhook as a structured response, making the workflow ideal for rapid database creation, data migration, and AI-assisted data modeling.\n\n---\n\n## Overview\n\nThis workflow automatically converts CSV or Excel files into a production-ready relational database schema using AI and rule-based validation.\n\nIt analyzes uploaded data to detect column types, relationships, and data quality, then generates a normalized schema with proper keys and constraints. The output includes SQL DDL scripts, ERD diagrams, a data dictionary, and a load plan.\n\nThis eliminates manual schema design and accelerates database setup from raw data.\n\n---\n\n## How It Works\n\n1. **File Upload (Webhook)**  \n   Accepts CSV or XLSX files and initializes workflow configuration such as thresholds and retry limits.\n\n2. **File Extraction**  \n   Detects file format and extracts rows into structured JSON format.\n\n3. **Data Cleaning & Profiling**  \n   Cleans data, removes duplicates, normalizes values, and computes column statistics such as null percentage and uniqueness.\n\n4. **Column Analysis Engine**  \n   Identifies candidate primary keys, analyzes cardinality, and suggests potential foreign key relationships.\n\n5. **AI Schema Generation**  \n   Uses an AI agent to design normalized tables, assign SQL data types, and define primary keys, foreign keys, and constraints.\n\n6. **Validation Layer**  \n   Validates schema integrity by checking data types, primary key uniqueness, foreign key overlap, and constraint consistency.\n\n7. **Revision Loop**  \n   If validation fails, the workflow sends feedback to the AI agent and regenerates the schema until it meets requirements.\n\n8. **Schema Output Generation**  \n   Generates SQL DDL scripts, ERD diagrams, a data dictionary, and a load plan.\n\n9. **Load Plan Engine**  \n   Determines the correct order for inserting data and detects circular dependencies.\n\n10. **Combine & Explain**  \n   Merges all outputs and optionally provides AI-generated explanations of schema decisions.\n\n11. **Response Output**  \n   Returns all generated artifacts as a structured JSON response via webhook.\n\n---\n\n## Setup Instructions\n\n1. Activate the workflow and copy the webhook URL  \n2. Send a POST request with a CSV or XLSX file  \n3. Configure OpenAI credentials for the AI agent  \n4. Adjust thresholds if needed (FK overlap, retries, confidence)  \n5. Execute the workflow and review outputs  \n\n---\n\n## Use Cases\n\n- Automatically generate database schemas from CSV/Excel files  \n- Accelerate data migration and onboarding pipelines  \n- Rapidly prototype relational database designs  \n- Reverse engineer structured schemas from raw datasets  \n- AI-assisted data modeling and normalization  \n\n---\n\n## Requirements\n\n- n8n (latest version recommended)  \n- OpenAI API credentials  \n- LangChain nodes enabled  \n- CSV or XLSX input file  ","workflow":{"meta":{"instanceId":"48aac30adfc5487a33ef16e0e958096f27cef40df3ed0febcbe1ca199e789786"},"nodes":[{"id":"45ed7648-24d5-4f61-a1fb-190aded55d7e","name":"Upload Files Webhook","type":"n8n-nodes-base.webhook","position":[-912,-64],"webhookId":"7a47278f-909e-4534-a1dc-3e4ef56aff84","parameters":{"path":"schema-builder","options":{"rawBody":true},"httpMethod":"POST","responseMode":"lastNode"},"typeVersion":2.1},{"id":"48ede746-2041-47b6-9644-d7c1ef211f4e","name":"Workflow Configuration","type":"n8n-nodes-base.set","position":[-608,-64],"parameters":{"options":{},"assignments":{"assignments":[{"id":"id-1","name":"fkOverlapThreshold","type":"number","value":0.7},{"id":"id-2","name":"maxNormalizationDepth","type":"number","value":3},{"id":"id-3","name":"minUniquePercentForPK","type":"number","value":0.95}]},"includeOtherFields":true},"typeVersion":3.4},{"id":"9566ffef-ce10-456f-b1a4-74dfab6848b3","name":"Check File Type","type":"n8n-nodes-base.if","position":[-336,-64],"parameters":{"options":{},"conditions":{"options":{"leftValue":"","caseSensitive":false,"typeValidation":"loose"},"combinator":"or","conditions":[{"id":"id-1","operator":{"type":"string","operation":"contains"},"leftValue":"={{ $json.data.mimeType }}","rightValue":"csv"},{"id":"id-2","operator":{"type":"string","operation":"contains"},"leftValue":"={{ $json.data.mimeType }}","rightValue":"text/csv"}]}},"typeVersion":2.3},{"id":"a8f93e71-bc03-447b-8a9d-6350c56bb872","name":"Extract CSV Data","type":"n8n-nodes-base.extractFromFile","position":[-128,-160],"parameters":{"options":{"includeEmptyCells":true}},"typeVersion":1.1},{"id":"9f96e5af-5be6-465c-981b-09a2dd126449","name":"Extract Excel Data","type":"n8n-nodes-base.extractFromFile","position":[-128,32],"parameters":{"options":{},"operation":"xlsx"},"typeVersion":1.1},{"id":"ac1cc4e4-09cc-4386-984c-42f99581713b","name":"Column Profiling Engine","type":"n8n-nodes-base.code","position":[144,-64],"parameters":{"jsCode":"// Column Profiling Engine - Comprehensive Data Analysis\n// Profiles each column with statistical analysis, pattern detection, and metadata\n\nconst crypto = require('crypto');\n\n// Helper function to generate UUID\nfunction generateUUID() {\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n    const r = Math.random() * 16 | 0;\n    const v = c === 'x' ? r : (r & 0x3 | 0x8);\n    return v.toString(16);\n  });\n}\n\n// Helper function to calculate entropy\nfunction calculateEntropy(values) {\n  const freq = {};\n  values.forEach(v => {\n    freq[v] = (freq[v] || 0) + 1;\n  });\n  \n  let entropy = 0;\n  const total = values.length;\n  Object.values(freq).forEach(count => {\n    const p = count / total;\n    entropy -= p * Math.log2(p);\n  });\n  \n  return entropy;\n}\n\n// Helper function to detect data type\nfunction detectDataType(value) {\n  if (value === null || value === undefined || value === '') return 'null';\n  if (typeof value === 'number') return 'number';\n  if (typeof value === 'boolean') return 'boolean';\n  \n  const str = String(value);\n  \n  // Check for date patterns\n  if (!isNaN(Date.parse(str)) && /\\d{4}-\\d{2}-\\d{2}|\\d{1,2}\\/\\d{1,2}\\/\\d{2,4}/.test(str)) {\n    return 'date';\n  }\n  \n  // Check for number\n  if (!isNaN(str) && str.trim() !== '') return 'number';\n  \n  // Check for email\n  if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(str)) return 'email';\n  \n  // Check for URL\n  if (/^https?:\\/\\/.+/.test(str)) return 'url';\n  \n  return 'string';\n}\n\n// Helper function to extract regex patterns\nfunction extractPatterns(values) {\n  const patterns = {};\n  \n  values.forEach(v => {\n    if (v === null || v === undefined || v === '') return;\n    \n    const str = String(v);\n    let pattern = str.replace(/\\d/g, '9').replace(/[a-z]/gi, 'A');\n    patterns[pattern] = (patterns[pattern] || 0) + 1;\n  });\n  \n  // Return top 5 patterns\n  return Object.entries(patterns)\n    .sort((a, b) => b[1] - a[1])\n    .slice(0, 5)\n    .map(([pattern, count]) => ({ pattern, count }));\n}\n\n// Helper function to detect candidate IDs\nfunction isCandidateID(columnName, values, uniquePercent) {\n  const name = columnName.toLowerCase();\n  \n  // Check if name suggests ID\n  if (name.includes('id') || name.includes('key') || name === 'pk') {\n    return true;\n  }\n  \n  // Check if values are unique and sequential or UUID-like\n  if (uniquePercent > 95) {\n    const sample = values.filter(v => v !== null && v !== undefined).slice(0, 100);\n    const hasUUID = sample.some(v => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(String(v)));\n    const hasSequential = sample.every((v, i) => i === 0 || Number(v) === Number(sample[i-1]) + 1);\n    \n    return hasUUID || hasSequential;\n  }\n  \n  return false;\n}\n\n// Helper function to detect reference columns\nfunction isReferenceColumn(columnName, values) {\n  const name = columnName.toLowerCase();\n  \n  // Check for foreign key naming patterns\n  if (name.endsWith('_id') || name.endsWith('id') && name !== 'id') {\n    return true;\n  }\n  \n  // Check for reference patterns\n  if (name.includes('ref') || name.includes('fk')) {\n    return true;\n  }\n  \n  return false;\n}\n\n// Helper function to detect composite key candidates\nfunction detectCompositeKeys(columns) {\n  const candidates = [];\n  \n  // Look for combinations of 2-3 columns that might form composite keys\n  for (let i = 0; i < columns.length; i++) {\n    for (let j = i + 1; j < columns.length; j++) {\n      const col1 = columns[i];\n      const col2 = columns[j];\n      \n      if (col1.uniquePercent < 100 && col2.uniquePercent < 100) {\n        candidates.push([col1.name, col2.name]);\n      }\n    }\n  }\n  \n  return candidates.slice(0, 5); // Return top 5 candidates\n}\n\n// Main profiling logic\nconst results = [];\n\nfor (const item of $input.all()) {\n  const data = item.json;\n  \n  // Extract metadata from input\n  const sourceFile = data.fileName || data.source_file || 'unknown';\n  const sheetName = data.sheetName || data.sheet_name || null;\n  \n  // Get the actual data rows\n  let rows = data.data || data.rows || [];\n  \n  // If data is in a different structure, try to extract it\n  if (rows.length === 0 && typeof data === 'object') {\n    // Check if data itself is an array\n    if (Array.isArray(data)) {\n      rows = data;\n    } else {\n      // Try to find array property\n      const arrayProp = Object.keys(data).find(key => Array.isArray(data[key]));\n      if (arrayProp) {\n        rows = data[arrayProp];\n      }\n    }\n  }\n  \n  if (rows.length === 0) {\n    continue;\n  }\n  \n  const rowCount = rows.length;\n  const datasetId = generateUUID();\n  \n  // Calculate file hash\n  const fileHash = crypto.createHash('md5').update(JSON.stringify(rows)).digest('hex');\n  \n  // Get column names\n  const columnNames = Object.keys(rows[0] || {});\n  \n  // Profile each column\n  const columnProfiles = [];\n  \n  for (const colName of columnNames) {\n    const values = rows.map(row => row[colName]);\n    \n    // Calculate null percentage\n    const nullCount = values.filter(v => v === null || v === undefined || v === '').length;\n    const nullPercent = (nullCount / rowCount) * 100;\n    \n    // Calculate unique percentage\n    const uniqueValues = new Set(values.filter(v => v !== null && v !== undefined && v !== ''));\n    const uniquePercent = (uniqueValues.size / (rowCount - nullCount)) * 100 || 0;\n    \n    // Get non-null values for analysis\n    const nonNullValues = values.filter(v => v !== null && v !== undefined && v !== '');\n    \n    // Detect data types\n    const typeDistribution = {};\n    nonNullValues.forEach(v => {\n      const type = detectDataType(v);\n      typeDistribution[type] = (typeDistribution[type] || 0) + 1;\n    });\n    \n    // Calculate min/max for numeric and string values\n    let minValue = null;\n    let maxValue = null;\n    \n    if (nonNullValues.length > 0) {\n      const numericValues = nonNullValues.filter(v => !isNaN(v)).map(Number);\n      if (numericValues.length > 0) {\n        minValue = Math.min(...numericValues);\n        maxValue = Math.max(...numericValues);\n      } else {\n        // For strings, use length\n        const lengths = nonNullValues.map(v => String(v).length);\n        minValue = Math.min(...lengths);\n        maxValue = Math.max(...lengths);\n      }\n    }\n    \n    // Calculate entropy\n    const entropy = nonNullValues.length > 0 ? calculateEntropy(nonNullValues) : 0;\n    \n    // Extract regex patterns\n    const patterns = extractPatterns(nonNullValues);\n    \n    // Create frequency histogram (top 10 values)\n    const frequencyMap = {};\n    nonNullValues.forEach(v => {\n      frequencyMap[v] = (frequencyMap[v] || 0) + 1;\n    });\n    \n    const frequencyHistogram = Object.entries(frequencyMap)\n      .sort((a, b) => b[1] - a[1])\n      .slice(0, 10)\n      .map(([value, count]) => ({ value, count, percentage: (count / nonNullValues.length) * 100 }));\n    \n    // Detect special column types\n    const isCandidateId = isCandidateID(colName, nonNullValues, uniquePercent);\n    const isReference = isReferenceColumn(colName, nonNullValues);\n    \n    // Check for repeated entity attributes (low cardinality)\n    const isRepeatedEntity = uniquePercent < 10 && uniquePercent > 0;\n    \n    columnProfiles.push({\n      name: colName,\n      nullPercent: Math.round(nullPercent * 100) / 100,\n      uniquePercent: Math.round(uniquePercent * 100) / 100,\n      minValue,\n      maxValue,\n      dataTypeDistribution: typeDistribution,\n      regexPatterns: patterns,\n      entropyScore: Math.round(entropy * 100) / 100,\n      frequencyHistogram,\n      isCandidateID: isCandidateId,\n      isReferenceColumn: isReference,\n      isRepeatedEntityAttribute: isRepeatedEntity,\n      sampleValues: nonNullValues.slice(0, 5)\n    });\n  }\n  \n  // Detect composite key candidates\n  const compositeKeyCandidates = detectCompositeKeys(columnProfiles);\n  \n  // Compile final result\n  results.push({\n    json: {\n      dataset_id: datasetId,\n      source_file: sourceFile,\n      sheet_name: sheetName,\n      row_count: rowCount,\n      file_hash: fileHash,\n      column_count: columnNames.length,\n      columns: columnProfiles,\n      composite_key_candidates: compositeKeyCandidates,\n      profiled_at: new Date().toISOString()\n    }\n  });\n}\n\nreturn results;"},"typeVersion":2},{"id":"5d76c141-176e-49e3-8f6b-a06854506a19","name":"Aggregate All Datasets","type":"n8n-nodes-base.aggregate","position":[336,-64],"parameters":{"options":{},"aggregate":"aggregateAllItemData","destinationFieldName":"datasets"},"typeVersion":1},{"id":"2730cfc0-2e65-41a5-b77f-31a03dbfd246","name":"Schema Reasoning Agent","type":"@n8n/n8n-nodes-langchain.agent","position":[544,-112],"parameters":{"text":"={{ JSON.stringify($json.datasets) }}","options":{"systemMessage":"You are a database schema architect specializing in data normalization and relational database design.\n\nYour task is to analyze the provided column profiling data from multiple datasets and design a normalized relational database schema.\n\n**Input Data:**\nYou will receive profiled datasets with column statistics including:\n- null %, unique %, min/max values\n- data type distribution\n- regex patterns and entropy scores\n- candidate IDs, composite keys, reference-like columns\n- repeated entity attributes\n- dataset metadata (source, row count, etc.)\n\n**Your Responsibilities:**\n\n1. **Entity Detection:** Identify logical entities by analyzing:\n   - Column name similarity across datasets\n   - Value reuse patterns\n   - Cardinality relationships\n   - Functional dependencies\n   - Repeated attribute groups\n\n2. **Table Design:** For each entity:\n   - Define table name (clear, singular)\n   - Assign columns to appropriate tables\n   - Identify primary key (must have ≥95% uniqueness)\n   - Avoid over-normalization and under-normalization\n\n3. **Relationship Inference:** Create foreign keys ONLY when:\n   - Value overlap ≥70% (use fkOverlapThreshold from config)\n   - Compatible data types\n   - Semantic header match\n   - No cycles in relationships\n\n4. **Constraints & Indexes:**\n   - Define NOT NULL constraints\n   - Add UNIQUE constraints where appropriate\n   - Suggest indexes for foreign keys and frequently queried columns\n\n5. **Validation Rules:**\n   - Ensure no duplicate PKs\n   - Verify FK overlap meets threshold\n   - Check type compatibility\n   - Prevent orphan tables\n\n**Output Requirements:**\nReturn a JSON schema with this exact structure:\n{\n  \"tables\": [\n    {\n      \"name\": \"table_name\",\n      \"columns\": [\n        {\n          \"name\": \"column_name\",\n          \"dataType\": \"SQL_TYPE\",\n          \"isPrimaryKey\": false,\n          \"nullable\": true,\n          \"isUnique\": false,\n          \"sourceColumns\": [\"original_column_name\"]\n        }\n      ]\n    }\n  ],\n  \"relationships\": [\n    {\n      \"sourceTable\": \"table_name\",\n      \"sourceColumn\": \"column_name\",\n      \"targetTable\": \"referenced_table\",\n      \"targetColumn\": \"referenced_column\",\n      \"overlapPercent\": 0.85\n    }\n  ],\n  \"indexes\": [\n    {\n      \"name\": \"idx_name\",\n      \"tableName\": \"table_name\",\n      \"columns\": [\"column_name\"],\n      \"isUnique\": false\n    }\n  ]\n}\n\n**Critical Rules:**\n- NO hallucinated columns\n- NO guessed relationships\n- ALL decisions must be data-backed\n- Use configuration thresholds from input"},"promptType":"define","hasOutputParser":true},"typeVersion":3},{"id":"ad9c1862-c897-4dcc-ae4e-ec820da8f522","name":"Schema JSON Parser","type":"@n8n/n8n-nodes-langchain.outputParserStructured","position":[736,32],"parameters":{"schemaType":"manual","inputSchema":"{\n  \"type\": \"object\",\n  \"properties\": {\n    \"tables\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"name\": {\n            \"type\": \"string\"\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"name\": {\n                  \"type\": \"string\"\n                },\n                \"type\": {\n                  \"type\": \"string\"\n                },\n                \"nullable\": {\n                  \"type\": \"boolean\"\n                },\n                \"primaryKey\": {\n                  \"type\": \"boolean\"\n                },\n                \"foreignKey\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"table\": {\n                      \"type\": \"string\"\n                    },\n                    \"column\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              },\n              \"required\": [\"name\", \"type\"]\n            }\n          }\n        },\n        \"required\": [\"name\", \"columns\"]\n      }\n    },\n    \"relationships\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"fromTable\": {\n            \"type\": \"string\"\n          },\n          \"fromColumn\": {\n            \"type\": \"string\"\n          },\n          \"toTable\": {\n            \"type\": \"string\"\n          },\n          \"toColumn\": {\n            \"type\": \"string\"\n          },\n          \"overlapPercent\": {\n            \"type\": \"number\"\n          }\n        },\n        \"required\": [\"fromTable\", \"fromColumn\", \"toTable\", \"toColumn\"]\n      }\n    },\n    \"indexes\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"table\": {\n            \"type\": \"string\"\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            }\n          },\n          \"type\": {\n            \"type\": \"string\"\n          }\n        },\n        \"required\": [\"table\", \"columns\", \"type\"]\n      }\n    }\n  },\n  \"required\": [\"tables\", \"relationships\", \"indexes\"]\n}"},"typeVersion":1.3},{"id":"bc033391-3d9c-4cc3-ab97-ad0276efe75d","name":"Rule Validator","type":"n8n-nodes-base.code","position":[912,-64],"parameters":{"jsCode":"// Rule Validator - Validates schema integrity and normalization rules\n\nconst items = $input.all();\nconst schemaData = items[0].json;\nconst config = $('Workflow Configuration').item.json;\n\n// Extract schema and configuration\nconst schema = schemaData.schema || schemaData;\nconst fkThreshold = config.fkOverlapThreshold || 0.8;\n\n// Initialize validation results\nconst validationResults = {\n  isValid: true,\n  errors: [],\n  warnings: [],\n  checks: {\n    primaryKeyUniqueness: { passed: true, details: [] },\n    foreignKeyOverlap: { passed: true, details: [] },\n    columnTypeCompatibility: { passed: true, details: [] },\n    noOrphanTables: { passed: true, details: [] },\n    noOverNormalization: { passed: true, details: [] },\n    noUnderNormalization: { passed: true, details: [] }\n  }\n};\n\n// Helper function to add error\nfunction addError(check, message) {\n  validationResults.isValid = false;\n  validationResults.errors.push(message);\n  validationResults.checks[check].passed = false;\n  validationResults.checks[check].details.push(message);\n}\n\n// Helper function to add warning\nfunction addWarning(check, message) {\n  validationResults.warnings.push(message);\n  validationResults.checks[check].details.push(message);\n}\n\n// 1. Check Primary Key Uniqueness\nfor (const table of schema.tables || []) {\n  const pkColumns = table.columns.filter(col => col.isPrimaryKey);\n  \n  if (pkColumns.length === 0) {\n    addError('primaryKeyUniqueness', `Table \"${table.name}\" has no primary key defined`);\n  }\n  \n  // Check for duplicate PK column names within the same table\n  const pkNames = pkColumns.map(col => col.name);\n  const duplicatePKs = pkNames.filter((name, index) => pkNames.indexOf(name) !== index);\n  \n  if (duplicatePKs.length > 0) {\n    addError('primaryKeyUniqueness', `Table \"${table.name}\" has duplicate primary key columns: ${duplicatePKs.join(', ')}`);\n  }\n}\n\n// 2. Check Foreign Key Overlap and Validity\nfor (const relationship of schema.relationships || []) {\n  const sourceTable = schema.tables.find(t => t.name === relationship.sourceTable);\n  const targetTable = schema.tables.find(t => t.name === relationship.targetTable);\n  \n  if (!sourceTable) {\n    addError('foreignKeyOverlap', `Source table \"${relationship.sourceTable}\" not found for relationship`);\n    continue;\n  }\n  \n  if (!targetTable) {\n    addError('foreignKeyOverlap', `Target table \"${relationship.targetTable}\" not found for relationship`);\n    continue;\n  }\n  \n  const fkColumn = sourceTable.columns.find(col => col.name === relationship.sourceColumn);\n  const pkColumn = targetTable.columns.find(col => col.name === relationship.targetColumn);\n  \n  if (!fkColumn) {\n    addError('foreignKeyOverlap', `Foreign key column \"${relationship.sourceColumn}\" not found in table \"${relationship.sourceTable}\"`);\n  }\n  \n  if (!pkColumn) {\n    addError('foreignKeyOverlap', `Referenced column \"${relationship.targetColumn}\" not found in table \"${relationship.targetTable}\"`);\n  }\n  \n  // Check if target column is a primary key\n  if (pkColumn && !pkColumn.isPrimaryKey) {\n    addWarning('foreignKeyOverlap', `Foreign key in \"${relationship.sourceTable}\" references non-primary key column \"${relationship.targetColumn}\" in \"${relationship.targetTable}\"`);\n  }\n}\n\n// 3. Check Column Type Compatibility for Foreign Keys\nfor (const relationship of schema.relationships || []) {\n  const sourceTable = schema.tables.find(t => t.name === relationship.sourceTable);\n  const targetTable = schema.tables.find(t => t.name === relationship.targetTable);\n  \n  if (sourceTable && targetTable) {\n    const fkColumn = sourceTable.columns.find(col => col.name === relationship.sourceColumn);\n    const pkColumn = targetTable.columns.find(col => col.name === relationship.targetColumn);\n    \n    if (fkColumn && pkColumn) {\n      // Normalize types for comparison\n      const fkType = fkColumn.dataType.toLowerCase().replace(/\\(.*\\)/, '');\n      const pkType = pkColumn.dataType.toLowerCase().replace(/\\(.*\\)/, '');\n      \n      if (fkType !== pkType) {\n        addError('columnTypeCompatibility', `Type mismatch: FK \"${relationship.sourceTable}.${relationship.sourceColumn}\" (${fkColumn.dataType}) does not match PK \"${relationship.targetTable}.${relationship.targetColumn}\" (${pkColumn.dataType})`);\n      }\n    }\n  }\n}\n\n// 4. Check for Orphan Tables (tables with no relationships)\nconst tablesWithRelationships = new Set();\nfor (const relationship of schema.relationships || []) {\n  tablesWithRelationships.add(relationship.sourceTable);\n  tablesWithRelationships.add(relationship.targetTable);\n}\n\nfor (const table of schema.tables || []) {\n  if (!tablesWithRelationships.has(table.name) && schema.tables.length > 1) {\n    addWarning('noOrphanTables', `Table \"${table.name}\" has no relationships with other tables (orphan table)`);\n  }\n}\n\n// 5. Check for Over-Normalization (too many tables with single columns)\nconst singleColumnTables = schema.tables.filter(table => \n  table.columns.filter(col => !col.isPrimaryKey).length <= 1\n);\n\nif (singleColumnTables.length > schema.tables.length * 0.3) {\n  addWarning('noOverNormalization', `Possible over-normalization: ${singleColumnTables.length} tables have only 1-2 columns. Consider consolidating: ${singleColumnTables.map(t => t.name).join(', ')}`);\n}\n\n// 6. Check for Under-Normalization (tables with too many columns)\nconst largeColumnTables = schema.tables.filter(table => table.columns.length > 15);\n\nif (largeColumnTables.length > 0) {\n  for (const table of largeColumnTables) {\n    addWarning('noUnderNormalization', `Table \"${table.name}\" has ${table.columns.length} columns. Consider normalizing if there are repeating groups or multi-valued dependencies`);\n  }\n}\n\n// Check for repeating column patterns (sign of under-normalization)\nfor (const table of schema.tables || []) {\n  const columnNames = table.columns.map(col => col.name.toLowerCase());\n  const patterns = {};\n  \n  for (const name of columnNames) {\n    // Check for numbered columns (e.g., phone1, phone2, phone3)\n    const match = name.match(/^(.+?)(\\d+)$/);\n    if (match) {\n      const base = match[1];\n      patterns[base] = (patterns[base] || 0) + 1;\n    }\n  }\n  \n  for (const [pattern, count] of Object.entries(patterns)) {\n    if (count >= 3) {\n      addWarning('noUnderNormalization', `Table \"${table.name}\" has repeating column pattern \"${pattern}*\" (${count} occurrences). Consider normalizing into a separate table`);\n    }\n  }\n}\n\n// Prepare output\nif (validationResults.isValid) {\n  return [\n    {\n      json: {\n        ...schemaData,\n        validation: {\n          status: 'passed',\n          timestamp: new Date().toISOString(),\n          checks: validationResults.checks,\n          warnings: validationResults.warnings\n        }\n      }\n    }\n  ];\n} else {\n  // Return error with validation details\n  return [\n    {\n      json: {\n        error: 'Schema validation failed',\n        validationStatus: 'failed',\n        errors: validationResults.errors,\n        warnings: validationResults.warnings,\n        checks: validationResults.checks,\n        originalSchema: schemaData\n      }\n    }\n  ];\n}"},"typeVersion":2},{"id":"a14dcff2-c7f6-4397-8c5b-d0fd66ff0d47","name":"SQL Schema Generator","type":"n8n-nodes-base.code","position":[1168,-64],"parameters":{"jsCode":"// SQL Schema Generator\n// Generates executable SQL CREATE TABLE, ALTER TABLE, and CREATE INDEX statements\n\nconst items = $input.all();\nconst validatedSchema = items[0].json;\n\n// Helper function to map data types to SQL types\nfunction mapToSQLType(dataType, maxLength = null) {\n  const typeMap = {\n    'string': maxLength && maxLength <= 255 ? `VARCHAR(${maxLength})` : 'TEXT',\n    'integer': 'INTEGER',\n    'bigint': 'BIGINT',\n    'decimal': 'DECIMAL(10,2)',\n    'float': 'FLOAT',\n    'double': 'DOUBLE',\n    'boolean': 'BOOLEAN',\n    'date': 'DATE',\n    'datetime': 'DATETIME',\n    'timestamp': 'TIMESTAMP',\n    'text': 'TEXT',\n    'json': 'JSON'\n  };\n  return typeMap[dataType.toLowerCase()] || 'VARCHAR(255)';\n}\n\n// Generate CREATE TABLE statements\nlet sqlScript = \"-- ============================================\\n\";\nsqlScript += \"-- SQL Schema Generated by AI Schema Builder\\n\";\nsqlScript += `-- Generated: ${new Date().toISOString()}\\n`;\nsqlScript += \"-- ============================================\\n\\n\";\n\n// First pass: Create all tables\nfor (const table of validatedSchema.tables) {\n  sqlScript += `-- Table: ${table.name}\\n`;\n  if (table.description) {\n    sqlScript += `-- Description: ${table.description}\\n`;\n  }\n  sqlScript += `CREATE TABLE ${table.name} (\\n`;\n  \n  const columnDefs = [];\n  \n  for (const column of table.columns) {\n    let colDef = `  ${column.name} ${mapToSQLType(column.dataType, column.maxLength)}`;\n    \n    // Add constraints\n    if (column.isPrimaryKey) {\n      colDef += ' PRIMARY KEY';\n    }\n    if (column.isUnique && !column.isPrimaryKey) {\n      colDef += ' UNIQUE';\n    }\n    if (!column.nullable) {\n      colDef += ' NOT NULL';\n    }\n    if (column.defaultValue !== undefined && column.defaultValue !== null) {\n      colDef += ` DEFAULT ${typeof column.defaultValue === 'string' ? `'${column.defaultValue}'` : column.defaultValue}`;\n    }\n    if (column.autoIncrement) {\n      colDef += ' AUTO_INCREMENT';\n    }\n    \n    columnDefs.push(colDef);\n  }\n  \n  sqlScript += columnDefs.join(',\\n');\n  sqlScript += \"\\n);\\n\\n\";\n}\n\n// Second pass: Add foreign key constraints\nif (validatedSchema.relationships && validatedSchema.relationships.length > 0) {\n  sqlScript += \"-- ============================================\\n\";\n  sqlScript += \"-- Foreign Key Relationships\\n\";\n  sqlScript += \"-- ============================================\\n\\n\";\n  \n  for (const rel of validatedSchema.relationships) {\n    sqlScript += `-- Relationship: ${rel.name || `${rel.fromTable}_${rel.toTable}`}\\n`;\n    if (rel.description) {\n      sqlScript += `-- Description: ${rel.description}\\n`;\n    }\n    \n    const constraintName = `fk_${rel.fromTable}_${rel.fromColumn}_${rel.toTable}_${rel.toColumn}`;\n    sqlScript += `ALTER TABLE ${rel.fromTable}\\n`;\n    sqlScript += `  ADD CONSTRAINT ${constraintName}\\n`;\n    sqlScript += `  FOREIGN KEY (${rel.fromColumn})\\n`;\n    sqlScript += `  REFERENCES ${rel.toTable}(${rel.toColumn})`;\n    \n    if (rel.onDelete) {\n      sqlScript += `\\n  ON DELETE ${rel.onDelete.toUpperCase()}`;\n    }\n    if (rel.onUpdate) {\n      sqlScript += `\\n  ON UPDATE ${rel.onUpdate.toUpperCase()}`;\n    }\n    \n    sqlScript += \";\\n\\n\";\n  }\n}\n\n// Third pass: Create indexes\nif (validatedSchema.indexes && validatedSchema.indexes.length > 0) {\n  sqlScript += \"-- ============================================\\n\";\n  sqlScript += \"-- Indexes\\n\";\n  sqlScript += \"-- ============================================\\n\\n\";\n  \n  for (const index of validatedSchema.indexes) {\n    sqlScript += `-- Index: ${index.name}\\n`;\n    if (index.description) {\n      sqlScript += `-- Description: ${index.description}\\n`;\n    }\n    \n    const indexType = index.isUnique ? 'UNIQUE INDEX' : 'INDEX';\n    const columns = Array.isArray(index.columns) ? index.columns.join(', ') : index.columns;\n    \n    sqlScript += `CREATE ${indexType} ${index.name}\\n`;\n    sqlScript += `  ON ${index.tableName}(${columns});\\n\\n`;\n  }\n}\n\nsqlScript += \"-- ============================================\\n\";\nsqlScript += \"-- End of Schema\\n\";\nsqlScript += \"-- ============================================\\n\";\n\nreturn [{\n  json: {\n    sqlSchema: sqlScript,\n    tableCount: validatedSchema.tables.length,\n    relationshipCount: validatedSchema.relationships?.length || 0,\n    indexCount: validatedSchema.indexes?.length || 0\n  }\n}];"},"typeVersion":2},{"id":"055c9b60-af59-4ece-ac00-e3a0550d9160","name":"Generate Data Dictionary & ERD","type":"n8n-nodes-base.code","position":[1440,-64],"parameters":{"jsCode":"// Get the SQL schema and validation results from previous nodes\nconst sqlSchema = $input.first().json.sqlSchema || [];\nconst validationResults = $input.first().json.validationResults || {};\nconst schemaAnalysis = $input.first().json.schemaAnalysis || {};\n\n// Generate Data Dictionary\nconst dataDictionary = [];\n\nfor (const table of sqlSchema) {\n  const tableName = table.tableName;\n  const columns = table.columns || [];\n  \n  for (const column of columns) {\n    const dictEntry = {\n      tableName: tableName,\n      columnName: column.name,\n      dataType: column.type,\n      constraints: [],\n      sourceColumns: column.sourceColumns || [],\n      description: column.description || ''\n    };\n    \n    // Add constraints\n    if (column.primaryKey) {\n      dictEntry.constraints.push('PRIMARY KEY');\n    }\n    if (column.notNull) {\n      dictEntry.constraints.push('NOT NULL');\n    }\n    if (column.unique) {\n      dictEntry.constraints.push('UNIQUE');\n    }\n    if (column.foreignKey) {\n      dictEntry.constraints.push(`FOREIGN KEY -> ${column.foreignKey.table}.${column.foreignKey.column}`);\n    }\n    \n    dataDictionary.push(dictEntry);\n  }\n}\n\n// Generate ERD in Mermaid format\nlet erdDiagram = 'erDiagram\\n';\n\n// Add tables and their columns\nfor (const table of sqlSchema) {\n  const tableName = table.tableName;\n  erdDiagram += `  ${tableName} {\\n`;\n  \n  for (const column of table.columns || []) {\n    let columnDef = `    ${column.type} ${column.name}`;\n    \n    // Add key indicators\n    if (column.primaryKey) {\n      columnDef += ' PK';\n    }\n    if (column.foreignKey) {\n      columnDef += ' FK';\n    }\n    if (column.unique) {\n      columnDef += ' \"UNIQUE\"';\n    }\n    if (column.notNull) {\n      columnDef += ' \"NOT NULL\"';\n    }\n    \n    erdDiagram += columnDef + '\\n';\n  }\n  \n  erdDiagram += '  }\\n';\n}\n\n// Add relationships\nconst relationships = [];\nfor (const table of sqlSchema) {\n  for (const column of table.columns || []) {\n    if (column.foreignKey) {\n      const relationship = {\n        from: table.tableName,\n        to: column.foreignKey.table,\n        cardinality: column.foreignKey.cardinality || 'many-to-one'\n      };\n      relationships.push(relationship);\n    }\n  }\n}\n\n// Add relationship lines to ERD\nfor (const rel of relationships) {\n  let cardinalitySymbol = '';\n  \n  switch (rel.cardinality) {\n    case 'one-to-one':\n      cardinalitySymbol = '||--||';\n      break;\n    case 'one-to-many':\n      cardinalitySymbol = '||--o{';\n      break;\n    case 'many-to-one':\n      cardinalitySymbol = '}o--||';\n      break;\n    case 'many-to-many':\n      cardinalitySymbol = '}o--o{';\n      break;\n    default:\n      cardinalitySymbol = '}o--||';\n  }\n  \n  erdDiagram += `  ${rel.from} ${cardinalitySymbol} ${rel.to} : \"references\"\\n`;\n}\n\n// Return the results\nreturn [\n  {\n    json: {\n      dataDictionary: dataDictionary,\n      erdDiagram: erdDiagram,\n      sqlSchema: sqlSchema,\n      validationResults: validationResults,\n      schemaAnalysis: schemaAnalysis\n    }\n  }\n];"},"typeVersion":2},{"id":"012da755-1b1a-46d5-a23c-cd18daceb1dd","name":"Return Results","type":"n8n-nodes-base.respondToWebhook","position":[1744,-64],"parameters":{"options":{"responseCode":200},"respondWith":"json","responseBody":"={\n  \"sqlSchema\": {{ $json.sqlSchema }},\n  \"dataDictionary\": {{ $json.dataDictionary }},\n  \"erdDiagram\": {{ $json.erdDiagram }}\n}"},"typeVersion":1.5},{"id":"3abc6b02-2765-4336-8d93-fe2b223611e5","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-960,-208],"parameters":{"color":7,"width":502,"height":288,"content":"## Input & Configuration\n\nHandles file upload via webhook and initializes schema generation settings such as thresholds and normalization rules."},"typeVersion":1},{"id":"e428ba61-4982-4a27-b547-7ef8028c99f1","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[864,-288],"parameters":{"color":7,"width":230,"height":432,"content":"## Schema Validation\n\nValidates the generated schema by checking keys, relationships, data types, and normalization issues."},"typeVersion":1},{"id":"04d213e4-2d19-45e2-ac2a-5a51ae9c024c","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[1664,-288],"parameters":{"color":7,"width":246,"height":416,"content":"## Final Output\n\nReturns the generated SQL schema, ERD, and data dictionary as a structured JSON response via webhook."},"typeVersion":1},{"id":"fa24b327-27b6-487d-8f25-7dcd2a6366be","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[480,-288],"parameters":{"color":7,"width":358,"height":432,"content":"## AI Schema Reasoning\n\nUses an AI agent to design a normalized relational schema with tables, relationships, and constraints based on data analysis."},"typeVersion":1},{"id":"8402104c-39f2-450b-8f6e-730f4b6b81af","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[-416,-288],"parameters":{"color":7,"width":470,"height":480,"content":"## File Detection & Extraction\n\nDetects file type (CSV or Excel) and converts the uploaded file into structured JSON data for processing."},"typeVersion":1},{"id":"680bff54-3b01-4a60-a865-b7fa45c7bc2c","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[96,-288],"parameters":{"color":7,"width":374,"height":432,"content":"## Column Profiling Engine and Dataset Aggregation\n\nAnalyzes dataset columns to detect data types, patterns, uniqueness, entropy, and potential keys or relationships and Combines all processed datasets"},"typeVersion":1},{"id":"2e63a4c3-983e-4400-9b6c-9f1473a7775a","name":"Sticky Note7","type":"n8n-nodes-base.stickyNote","position":[-1552,-304],"parameters":{"width":368,"height":464,"content":"## AI Multi-Table Schema Builder with Automated Normalization and Relationship Detection\n\n## How it works\nThis workflow converts CSV/XLSX files into a fully validated database schema. It extracts and profiles data using statistical analysis, detects patterns and relationships, and uses an AI agent to design normalized tables. The schema is validated against strict rules before generating SQL scripts, ERD diagrams, and a data dictionary, which are returned via webhook.\n\n## Setup steps\n1. Activate webhook and upload CSV/XLSX file  \n2. Configure OpenAI (GPT-4) credentials  \n3. Adjust thresholds (FK overlap, PK uniqueness)  \n4. Execute workflow  \n5. Use generated SQL, ERD, and dictionary  "},"typeVersion":1},{"id":"27796da6-c284-4dbd-ae5f-afe8198dfd91","name":"OpenAI GPT","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[512,32],"parameters":{"model":{"__rl":true,"mode":"id","value":"gpt-4o"},"options":{},"builtInTools":{}},"typeVersion":1.3},{"id":"1ed03458-6bee-4d0c-8c3c-c28dcd0ca04e","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[1120,-288],"parameters":{"color":7,"width":230,"height":432,"content":"## SQL Schema Generation\n\nGenerates executable SQL scripts including table creation, constraints, and indexes."},"typeVersion":1},{"id":"af162567-336c-4616-9846-97bbef682013","name":"Sticky Note8","type":"n8n-nodes-base.stickyNote","position":[1376,-288],"parameters":{"color":7,"width":230,"height":432,"content":"## ERD & Data Dictionary\n\nCreates a visual ERD and a detailed data dictionary describing tables, columns, and relationships."},"typeVersion":1}],"pinData":{},"connections":{"OpenAI GPT":{"ai_languageModel":[[{"node":"Schema Reasoning Agent","type":"ai_languageModel","index":0}]]},"Rule Validator":{"main":[[{"node":"SQL Schema Generator","type":"main","index":0}]]},"Check File Type":{"main":[[{"node":"Extract CSV Data","type":"main","index":0}],[{"node":"Extract Excel Data","type":"main","index":0}]]},"Extract CSV Data":{"main":[[{"node":"Column Profiling Engine","type":"main","index":0}]]},"Extract Excel Data":{"main":[[{"node":"Column Profiling Engine","type":"main","index":0}]]},"Schema JSON Parser":{"ai_outputParser":[[{"node":"Schema Reasoning Agent","type":"ai_outputParser","index":0}]]},"SQL Schema Generator":{"main":[[{"node":"Generate Data Dictionary & ERD","type":"main","index":0}]]},"Upload Files Webhook":{"main":[[{"node":"Workflow Configuration","type":"main","index":0}]]},"Aggregate All Datasets":{"main":[[{"node":"Schema Reasoning Agent","type":"main","index":0}]]},"Schema Reasoning Agent":{"main":[[{"node":"Rule Validator","type":"main","index":0}]]},"Workflow Configuration":{"main":[[{"node":"Check File Type","type":"main","index":0}]]},"Column Profiling Engine":{"main":[[{"node":"Aggregate All Datasets","type":"main","index":0}]]},"Generate Data Dictionary & ERD":{"main":[[{"node":"Return Results","type":"main","index":0}]]}}},"lastUpdatedBy":1,"workflowInfo":{"nodeCount":23,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.set":{"count":1},"n8n-nodes-base.code":{"count":4},"n8n-nodes-base.webhook":{"count":1},"n8n-nodes-base.aggregate":{"count":1},"n8n-nodes-base.stickyNote":{"count":9},"@n8n/n8n-nodes-langchain.agent":{"count":1},"n8n-nodes-base.extractFromFile":{"count":2},"n8n-nodes-base.respondToWebhook":{"count":1},"@n8n/n8n-nodes-langchain.lmChatOpenAi":{"count":1},"@n8n/n8n-nodes-langchain.outputParserStructured":{"count":1}}},"status":"published","readyToDemo":null,"user":{"name":"ResilNext","username":"rnair1996","bio":"","verified":true,"links":[""],"avatar":"https://gravatar.com/avatar/c20bc6c3bcdf260fac3c28c556a8db661ee93670037a3ceb857e047851f6f438?r=pg&d=retro&size=200"},"nodes":[{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","codex":{"data":{"alias":["Router","Filter","Condition","Logic","Boolean","Branch"],"details":"The IF node can be used to implement binary conditional logic in your workflow. You can set up one-to-many conditions to evaluate each item of data being inputted into the node. That data will either evaluate to TRUE or FALSE and route out of the node accordingly.\n\nThis node has multiple types of conditions: Bool, String, Number, and Date & Time.","resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/create-a-toxic-language-detector-for-telegram/","icon":"🤬","label":"Create a toxic language detector for Telegram in 4 step"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automation-for-maintainers-of-open-source-projects/","icon":"🏷️","label":"How to automatically manage contributions to open-source projects"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/","icon":"🙌","label":"Sending Automated Congratulations with Google Sheets, Twilio, and n8n "},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.if/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow"]}}},"group":"[\"transform\"]","defaults":{"name":"If","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"displayName":"If","typeVersion":2,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":38,"icon":"fa:pen","name":"n8n-nodes-base.set","codex":{"data":{"alias":["Set","JS","JSON","Filter","Transform","Map"],"resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/database-monitoring-and-alerting-with-n8n/","icon":"📡","label":"Database Monitoring and Alerting with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/","icon":"📹","label":"The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/","icon":"🧰","label":"Learn to Build Powerful API Endpoints Using Webhooks"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Data Transformation"]}}},"group":"[\"input\"]","defaults":{"name":"Edit Fields"},"iconData":{"icon":"pen","type":"icon"},"displayName":"Edit Fields (Set)","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":47,"icon":"file:webhook.svg","name":"n8n-nodes-base.webhook","codex":{"data":{"alias":["HTTP","API","Build","WH"],"resources":{"generic":[{"url":"https://n8n.io/blog/learn-how-to-automatically-cross-post-your-content-with-n8n/","icon":"✍️","label":"Learn how to automatically cross-post your content with n8n"},{"url":"https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/","icon":"🛳","label":"Running n8n on ships: An interview with Maranics"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/","icon":" 🪢","label":"What are APIs and how to use them with no code"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/how-a-digital-strategist-uses-n8n-for-online-marketing/","icon":"💻","label":"How a digital strategist uses n8n for online marketing"},{"url":"https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/","icon":"📹","label":"The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n"},{"url":"https://n8n.io/blog/how-to-automatically-give-kudos-to-contributors-with-github-slack-and-n8n/","icon":"👏","label":"How to automatically give kudos to contributors with GitHub, Slack, and n8n"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/creating-custom-incident-response-workflows-with-n8n/","label":"How to automate every step of an incident response workflow"},{"url":"https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/","icon":"🧰","label":"Learn to Build Powerful API Endpoints Using Webhooks"},{"url":"https://n8n.io/blog/learn-how-to-use-webhooks-with-mattermost-slash-commands/","icon":"🦄","label":"Learn how to use webhooks with Mattermost slash commands"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"trigger\"]","defaults":{"name":"Webhook"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTM1IDM3Yy0yLjIgMC00LTEuOC00LTRzMS44LTQgNC00IDQgMS44IDQgNC0xLjggNC00IDQiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUgNDNjLTMgMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEgMS40IDIuOSAyLjMgNC43IDIuMyAzLjMgMCA2LTIuNyA2LTZzLTIuNy02LTYtNmMtMSAwLTIgLjMtMi45LjdsLTEuNyAxTDIzLjMgMTZsMy41LTEuOSA1LjMgOS40YzEtLjMgMi0uNSAzLS41IDUuNSAwIDEwIDQuNSAxMCAxMFM0MC41IDQzIDM1IDQzIi8+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTE0IDQzQzguNSA0MyA0IDM4LjUgNCAzM2MwLTQuNiAzLjEtOC41IDcuNS05LjdsMSAzLjlDOS45IDI3LjkgOCAzMC4zIDggMzNjMCAzLjMgMi43IDYgNiA2czYtMi43IDYtNnYtMmgxNXY0SDIzLjhjLS45IDQuNi01IDgtOS44IDgiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTQgMzdjLTIuMiAwLTQtMS44LTQtNHMxLjgtNCA0LTQgNCAxLjggNCA0LTEuOCA0LTQgNCIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSAxOWMtMi4yIDAtNC0xLjgtNC00czEuOC00IDQtNCA0IDEuOCA0IDQtMS44IDQtNCA0Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0ibTE1LjcgMzQtMy40LTIgNS45LTkuN2MtMi0xLjktMy4yLTQuNS0zLjItNy4zIDAtNS41IDQuNS0xMCAxMC0xMHMxMCA0LjUgMTAgMTBjMCAuOS0uMSAxLjctLjMgMi41bC0zLjktMWMuMS0uNS4yLTEgLjItMS41IDAtMy4zLTIuNy02LTYtNnMtNiAyLjctNiA2YzAgMi4xIDEuMSA0IDIuOSA1LjFsMS43IDF6Ii8+PC9zdmc+"},"displayName":"Webhook","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":535,"icon":"file:webhook.svg","name":"n8n-nodes-base.respondToWebhook","codex":{"data":{"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.respondtowebhook/"}]},"categories":["Core Nodes","Utility"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"transform\"]","defaults":{"name":"Respond to Webhook"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTM1IDM3Yy0yLjIgMC00LTEuOC00LTRzMS44LTQgNC00IDQgMS44IDQgNC0xLjggNC00IDQiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUgNDNjLTMgMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEgMS40IDIuOSAyLjMgNC43IDIuMyAzLjMgMCA2LTIuNyA2LTZzLTIuNy02LTYtNmMtMSAwLTIgLjMtMi45LjdsLTEuNyAxTDIzLjMgMTZsMy41LTEuOSA1LjMgOS40YzEtLjMgMi0uNSAzLS41IDUuNSAwIDEwIDQuNSAxMCAxMFM0MC41IDQzIDM1IDQzIi8+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTE0IDQzQzguNSA0MyA0IDM4LjUgNCAzM2MwLTQuNiAzLjEtOC41IDcuNS05LjdsMSAzLjlDOS45IDI3LjkgOCAzMC4zIDggMzNjMCAzLjMgMi43IDYgNiA2czYtMi43IDYtNnYtMmgxNXY0SDIzLjhjLS45IDQuNi01IDgtOS44IDgiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTQgMzdjLTIuMiAwLTQtMS44LTQtNHMxLjgtNCA0LTQgNCAxLjggNCA0LTEuOCA0LTQgNCIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSAxOWMtMi4yIDAtNC0xLjgtNC00czEuOC00IDQtNCA0IDEuOCA0IDQtMS44IDQtNCA0Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0ibTE1LjcgMzQtMy40LTIgNS45LTkuN2MtMi0xLjktMy4yLTQuNS0zLjItNy4zIDAtNS41IDQuNS0xMCAxMC0xMHMxMCA0LjUgMTAgMTBjMCAuOS0uMSAxLjctLjMgMi41bC0zLjktMWMuMS0uNS4yLTEgLjItMS41IDAtMy4zLTIuNy02LTYtNnMtNiAyLjctNiA2YzAgMi4xIDEuMSA0IDIuOSA1LjFsMS43IDF6Ii8+PC9zdmc+"},"displayName":"Respond to Webhook","typeVersion":2,"nodeCategories":[{"id":7,"name":"Utility"},{"id":9,"name":"Core Nodes"}]},{"id":565,"icon":"fa:sticky-note","name":"n8n-nodes-base.stickyNote","codex":{"data":{"alias":["Comments","Notes","Sticky"],"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"input\"]","defaults":{"name":"Sticky Note","color":"#FFD233"},"iconData":{"icon":"sticky-note","type":"icon"},"displayName":"Sticky Note","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":834,"icon":"file:code.svg","name":"n8n-nodes-base.code","codex":{"data":{"alias":["cpde","Javascript","JS","Python","Script","Custom Code","Function"],"details":"The Code node allows you to execute JavaScript in your workflow.","resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers","Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Code"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTcxXzQ0MSkiPgo8cGF0aCBkPSJNMTcwLjI4MyA0OEgxOTYuNUMyMDMuMTI3IDQ4IDIwOC41IDQyLjYyNzQgMjA4LjUgMzZWMTJDMjA4LjUgNS4zNzI1OCAyMDMuMTI3IDAgMTk2LjUgMEgxNzAuMjgzQzEyNi4xIDAgOTAuMjgzIDM1LjgxNzIgOTAuMjgzIDgwVjE3NkM5MC4yODMgMjA2LjkyOCA2NS4yMTA5IDIzMiAzNC4yODMgMjMySDIzQzE2LjM3MjYgMjMyIDExIDIzNy4zNzIgMTEgMjQ0VjI2OEMxMSAyNzQuNjI3IDE2LjM3MjQgMjgwIDIyLjk5OTYgMjgwTDM0LjI4MyAyODBDNjUuMjEwOSAyODAgOTAuMjgzIDMwNS4wNzIgOTAuMjgzIDMzNlY0NDBDOTAuMjgzIDQ3OS43NjQgMTIyLjUxOCA1MTIgMTYyLjI4MyA1MTJIMTk2LjVDMjAzLjEyNyA1MTIgMjA4LjUgNTA2LjYyNyAyMDguNSA1MDBWNDc2QzIwOC41IDQ2OS4zNzMgMjAzLjEyNyA0NjQgMTk2LjUgNDY0SDE2Mi4yODNDMTQ5LjAyOCA0NjQgMTM4LjI4MyA0NTMuMjU1IDEzOC4yODMgNDQwVjMzNkMxMzguMjgzIDMwOS4wMjIgMTI4LjAxMSAyODQuNDQzIDExMS4xNjQgMjY1Ljk2MUMxMDYuMTA5IDI2MC40MTYgMTA2LjEwOSAyNTEuNTg0IDExMS4xNjQgMjQ2LjAzOUMxMjguMDExIDIyNy41NTcgMTM4LjI4MyAyMDIuOTc4IDEzOC4yODMgMTc2VjgwQzEzOC4yODMgNjIuMzI2OSAxNTIuNjEgNDggMTcwLjI4MyA0OFoiIGZpbGw9IiNGRjk5MjIiLz4KPHBhdGggZD0iTTMwNSAzNkMzMDUgNDIuNjI3NCAzMTAuMzczIDQ4IDMxNyA0OEgzNDIuOTc5QzM2MC42NTIgNDggMzc0Ljk3OCA2Mi4zMjY5IDM3NC45NzggODBWMTc2QzM3NC45NzggMjAyLjk3OCAzODUuMjUxIDIyNy41NTcgNDAyLjA5OCAyNDYuMDM5QzQwNy4xNTMgMjUxLjU4NCA0MDcuMTUzIDI2MC40MTYgNDAyLjA5OCAyNjUuOTYxQzM4NS4yNTEgMjg0LjQ0MyAzNzQuOTc4IDMwOS4wMjIgMzc0Ljk3OCAzMzZWNDMyQzM3NC45NzggNDQ5LjY3MyAzNjAuNjUyIDQ2NCAzNDIuOTc5IDQ2NEgzMTdDMzEwLjM3MyA0NjQgMzA1IDQ2OS4zNzMgMzA1IDQ3NlY1MDBDMzA1IDUwNi42MjcgMzEwLjM3MyA1MTIgMzE3IDUxMkgzNDIuOTc5QzM4Ny4xNjEgNTEyIDQyMi45NzggNDc2LjE4MyA0MjIuOTc4IDQzMlYzMzZDNDIyLjk3OCAzMDUuMDcyIDQ0OC4wNTEgMjgwIDQ3OC45NzkgMjgwSDQ5MEM0OTYuNjI3IDI4MCA1MDIgMjc0LjYyOCA1MDIgMjY4VjI0NEM1MDIgMjM3LjM3MyA0OTYuNjI4IDIzMiA0OTAgMjMyTDQ3OC45NzkgMjMyQzQ0OC4wNTEgMjMyIDQyMi45NzggMjA2LjkyOCA0MjIuOTc4IDE3NlY4MEM0MjIuOTc4IDM1LjgxNzIgMzg3LjE2MSAwIDM0Mi45NzkgMEgzMTdDMzEwLjM3MyAwIDMwNSA1LjM3MjU4IDMwNSAxMlYzNloiIGZpbGw9IiNGRjk5MjIiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF8xMTcxXzQ0MSI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="},"displayName":"Code","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":1119,"icon":"fa:robot","name":"@n8n/n8n-nodes-langchain.agent","codex":{"data":{"alias":["LangChain","Chat","Conversational","Plan and Execute","ReAct","Tools"],"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/"}]},"categories":["AI","Langchain"],"subcategories":{"AI":["Agents","Root Nodes"]}}},"group":"[\"transform\"]","defaults":{"name":"AI Agent","color":"#404040"},"iconData":{"icon":"robot","type":"icon"},"displayName":"AI Agent","typeVersion":3,"nodeCategories":[{"id":25,"name":"AI"},{"id":26,"name":"Langchain"}]},{"id":1153,"icon":"file:openAiLight.svg","name":"@n8n/n8n-nodes-langchain.lmChatOpenAi","codex":{"data":{"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.lmchatopenai/"}]},"categories":["AI","Langchain"],"subcategories":{"AI":["Language Models","Root Nodes"],"Language Models":["Chat Models (Recommended)"]}}},"group":"[\"transform\"]","defaults":{"name":"OpenAI Chat Model"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTM2Ljg2NzEgMTYuMzcxOEMzNy43NzQ2IDEzLjY0OCAzNy40NjIxIDEwLjY2NDIgMzYuMDEwOCA4LjE4NjYxQzMzLjgyODIgNC4zODY1MyAyOS40NDA3IDIuNDMxNDkgMjUuMTU1NiAzLjM1MTUxQzIzLjI0OTMgMS4yMDM5NiAyMC41MTA1IC0wLjAxNzMxNDggMTcuNjM5MiAwLjAwMDE4NTUzM0MxMy4yNTkxIC0wLjAwOTgxNDY4IDkuMzcyNzMgMi44MTAyNSA4LjAyNTIgNi45Nzc4M0M1LjIxMTM5IDcuNTU0MSAyLjc4MjU4IDkuMzE1MzggMS4zNjEzIDExLjgxMTdDLTAuODM3NDkzIDE1LjYwMTggLTAuMzM2MjMyIDIwLjM3OTQgMi42MDEzMyAyMy42Mjk0QzEuNjkzODEgMjYuMzUzMiAyLjAwNjMyIDI5LjMzNzEgMy40NTc2IDMxLjgxNDZDNS42NDAxNSAzNS42MTQ3IDEwLjAyNzcgMzcuNTY5NyAxNC4zMTI4IDM2LjY0OTdDMTYuMjE3OSAzOC43OTczIDE4Ljk1NzkgNDAuMDE4NSAyMS44MjkyIDM5Ljk5OThDMjYuMjExOCA0MC4wMTEgMzAuMDk5NCAzNy4xODg1IDMxLjQ0NjkgMzMuMDE3MUMzNC4yNjA4IDMyLjQ0MDkgMzYuNjg5NiAzMC42Nzk2IDM4LjExMDggMjguMTgzM0M0MC4zMDcxIDI0LjM5MzIgMzkuODA0NiAxOS42MTk0IDM2Ljg2ODMgMTYuMzY5M0wzNi44NjcxIDE2LjM3MThaTTIxLjgzMTcgMzcuMzg2QzIwLjA3OCAzNy4zODg1IDE4LjM3OTIgMzYuNzc0NyAxNy4wMzI5IDM1LjY1MDlDMTcuMDk0MSAzNS42MTg0IDE3LjIwMDQgMzUuNTU5NyAxNy4yNjkxIDM1LjUxNzJMMjUuMjM0MyAzMC45MTcxQzI1LjY0MTggMzAuNjg1OCAyNS44OTE4IDMwLjI1MjEgMjUuODg5MyAyOS43ODMzVjE4LjU1NDNMMjkuMjU1NyAyMC40OTgxQzI5LjI5MTkgMjAuNTE1NiAyOS4zMTU3IDIwLjU1MDYgMjkuMzIwNyAyMC41OTA2VjI5Ljg4OTZDMjkuMzE1NyAzNC4wMjQ3IDI1Ljk2NjggMzcuMzc3MiAyMS44MzE3IDM3LjM4NlpNNS43MjY0IDMwLjUwNzFDNC44NDc2MyAyOC45ODk2IDQuNTMxMzcgMjcuMjEwOCA0LjgzMjYzIDI1LjQ4NDVDNC44OTEzOCAyNS41MTk1IDQuOTk1MTMgMjUuNTgzMiA1LjA2ODg4IDI1LjYyNTdMMTMuMDM0MSAzMC4yMjU4QzEzLjQzNzggMzAuNDYyMSAxMy45Mzc4IDMwLjQ2MjEgMTQuMzQyOCAzMC4yMjU4TDI0LjA2NjggMjQuNjEwN1YyOC40OTgzQzI0LjA2OTMgMjguNTM4MyAyNC4wNTA1IDI4LjU3NyAyNC4wMTkzIDI4LjYwMkwxNS45Njc5IDMzLjI1MDlDMTIuMzgxNSAzNS4zMTU5IDcuODAxNDQgMzQuMDg4NCA1LjcyNzY1IDMwLjUwNzFINS43MjY0Wk0zLjYzMDEgMTMuMTIwNUM0LjUwNTEyIDExLjYwMDQgNS44ODY0IDEwLjQzNzkgNy41MzE0NCA5LjgzNDE1QzcuNTMxNDQgOS45MDI5IDcuNTI3NjkgMTAuMDI0MiA3LjUyNzY5IDEwLjEwOTJWMTkuMzEwNkM3LjUyNTE5IDE5Ljc3ODEgNy43NzUxOSAyMC4yMTE5IDguMTgxNDUgMjAuNDQzMUwxNy45MDU0IDI2LjA1N0wxNC41MzkxIDI4LjAwMDhDMTQuNTA1MyAyOC4wMjMzIDE0LjQ2MjggMjguMDI3IDE0LjQyNTMgMjguMDEwOEw2LjM3MjY2IDIzLjM1ODJDMi43OTM4MyAyMS4yODU2IDEuNTY2MzEgMTYuNzA2OCAzLjYyODg1IDEzLjEyMTdMMy42MzAxIDEzLjEyMDVaTTMxLjI4ODIgMTkuNTU2OUwyMS41NjQyIDEzLjk0MTdMMjQuOTMwNiAxMS45OTkyQzI0Ljk2NDMgMTEuOTc2NyAyNS4wMDY4IDExLjk3MjkgMjUuMDQ0MyAxMS45ODkyTDMzLjA5NyAxNi42MzhDMzYuNjgyMSAxOC43MDkzIDM3LjkxMDggMjMuMjk1NyAzNS44Mzk1IDI2Ljg4MDhDMzQuOTYzMyAyOC4zOTgzIDMzLjU4MzIgMjkuNTYwOCAzMS45Mzk1IDMwLjE2NThWMjAuNjg5NEMzMS45NDMyIDIwLjIyMTkgMzEuNjk0NSAxOS43ODk0IDMxLjI4OTQgMTkuNTU2OUgzMS4yODgyWk0zNC42MzgzIDE0LjUxNDJDMzQuNTc5NSAxNC40NzggMzQuNDc1OCAxNC40MTU1IDM0LjQwMiAxNC4zNzNMMjYuNDM2OCA5Ljc3Mjg5QzI2LjAzMzEgOS41MzY2NCAyNS41MzMxIDkuNTM2NjQgMjUuMTI4MSA5Ljc3Mjg5TDE1LjQwNDEgMTUuMzg4VjExLjUwMDRDMTUuNDAxNiAxMS40NjA0IDE1LjQyMDQgMTEuNDIxNyAxNS40NTE2IDExLjM5NjdMMjMuNTAzIDYuNzUxNThDMjcuMDg5NCA0LjY4Mjc5IDMxLjY3NDUgNS45MTQwNiAzMy43NDIgOS41MDE2NEMzNC42MTU4IDExLjAxNjcgMzQuOTMyIDEyLjc5MDUgMzQuNjM1OCAxNC41MTQySDM0LjYzODNaTTEzLjU3NDEgMjEuNDQzMUwxMC4yMDY1IDE5LjQ5OTRDMTAuMTcwMiAxOS40ODE5IDEwLjE0NjUgMTkuNDQ2OCAxMC4xNDE1IDE5LjQwNjhWMTAuMTA3OUMxMC4xNDQgNS45Njc4MSAxMy41MDI4IDIuNjEyNzQgMTcuNjQyOSAyLjYxNTI0QzE5LjM5NDIgMi42MTUyNCAyMS4wODkyIDMuMjMwMjUgMjIuNDM1NSA0LjM1MDI4QzIyLjM3NDMgNC4zODI3OCAyMi4yNjkzIDQuNDQxNTMgMjIuMTk5MiA0LjQ4NDAzTDE0LjIzNDEgOS4wODQxM0MxMy44MjY2IDkuMzE1MzggMTMuNTc2NiA5Ljc0Nzg5IDEzLjU3OTEgMTAuMjE2N0wxMy41NzQxIDIxLjQ0MDZWMjEuNDQzMVpNMTUuNDAyOSAxNy41MDA2TDE5LjczNDIgMTQuOTk5M0wyNC4wNjU1IDE3LjQ5OTNWMjIuNTAwN0wxOS43MzQyIDI1LjAwMDdMMTUuNDAyOSAyMi41MDA3VjE3LjUwMDZaIiBmaWxsPSIjN0Q3RDg3Ii8+Cjwvc3ZnPgo="},"displayName":"OpenAI Chat Model","typeVersion":1,"nodeCategories":[{"id":25,"name":"AI"},{"id":26,"name":"Langchain"}]},{"id":1179,"icon":"fa:code","name":"@n8n/n8n-nodes-langchain.outputParserStructured","codex":{"data":{"alias":["json","zod"],"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.outputparserstructured/"}]},"categories":["AI","Langchain"],"subcategories":{"AI":["Output Parsers"]}}},"group":"[\"transform\"]","defaults":{"name":"Structured Output Parser"},"iconData":{"icon":"code","type":"icon"},"displayName":"Structured Output Parser","typeVersion":1,"nodeCategories":[{"id":25,"name":"AI"},{"id":26,"name":"Langchain"}]},{"id":1235,"icon":"file:extractFromFile.svg","name":"n8n-nodes-base.extractFromFile","codex":{"data":{"alias":["CSV","Spreadsheet","Excel","xls","xlsx","ods","tabular","decode","decoding","Move Binary Data","Binary","File","PDF","JSON","HTML","ICS","iCal","txt","Text","RTF","XML","64","Base64","Convert"],"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.extractfromfile/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Files","Data Transformation"]}}},"group":"[\"input\"]","defaults":{"name":"Extract from File"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAuOTM3NSAyQzAuNDE5NzMzIDIgMCAyLjQxOTczIDAgMi45Mzc1VjM3LjMyMjFDMCAzNy44Mzk5IDAuNDE5NzMzIDM4LjI1OTYgMC45Mzc1IDM4LjI1OTZIMjYuMjE1NEMyNi43MzMyIDM4LjI1OTYgMjcuMTUyOSAzNy44Mzk5IDI3LjE1MjkgMzcuMzIyMUwyNy4xNTI5IDMwLjY3MTlMMTYuNzk2OSAzMC42NzE5QzE0Ljg5ODQgMzAuNjcxOSAxMy4zNTk0IDI5LjEzMjkgMTMuMzU5NCAyNy4yMzQ0VjI1LjM1OTRDMTMuMzU5NCAyMy40NjA5IDE0Ljg5ODQgMjEuOTIxOSAxNi43OTY5IDIxLjkyMTlIMjcuMTUyOUwyNy4xNTI5IDE1Ljc4MjFIMTQuMzA4M0MxMy43OTA2IDE1Ljc4MjEgMTMuMzcwOCAxNS4zNjI0IDEzLjM3MDggMTQuODQ0NlYySDAuOTM3NVoiIGZpbGw9IiMzNTNGNkUiLz4KPHBhdGggZD0iTTE2LjAyNzEgMkMxNS45NDA4IDIgMTUuODcwOCAyLjA2OTk2IDE1Ljg3MDggMi4xNTYyNVYxMi44MTM0QzE1Ljg3MDggMTMuMDcyMyAxNi4wODA3IDEzLjI4MjEgMTYuMzM5NiAxMy4yODIxSDI2Ljk5NjdDMjcuMDgzIDEzLjI4MjEgMjcuMTUyOSAxMy4yMTIyIDI3LjE1MjkgMTMuMTI1OUwyNy4xNTI5IDEyLjYxNzFDMjcuMTUyOSAxMi4zNjg4IDI3LjA1NDUgMTIuMTMwNyAyNi44NzkxIDExLjk1NUwxNy4yMjI1IDIuMjc1MzhDMTcuMDQ2NiAyLjA5OTA4IDE2LjgwNzkgMiAxNi41NTg4IDJIMTYuMDI3MVoiIGZpbGw9IiMzNTNGNkUiLz4KPHBhdGggZD0iTTI5Ljc2NDIgMzQuNjUwM0MyOS4wMzQgMzMuOTE2IDI5LjAzNzQgMzIuNzI4OCAyOS43NzE2IDMxLjk5ODZMMzMuNjE5NyAyOC4xNzE5TDE2Ljc5NjkgMjguMTcxOUMxNi4yNzkxIDI4LjE3MTkgMTUuODU5NCAyNy43NTIxIDE1Ljg1OTQgMjcuMjM0NFYyNS4zNTk0QzE1Ljg1OTQgMjQuODQxNiAxNi4yNzkxIDI0LjQyMTkgMTYuNzk2OSAyNC40MjE5TDMzLjU0MTIgMjQuNDIxOUwyOS43NzE2IDIwLjY3MzNDMjkuMDM3NCAxOS45NDMxIDI5LjAzNCAxOC43NTU5IDI5Ljc2NDIgMTguMDIxNkMzMC40OTQ0IDE3LjI4NzQgMzEuNjgxNiAxNy4yODQgMzIuNDE1OSAxOC4wMTQyTDM5LjQ0NzEgMjUuMDA2NEMzOS44MDEgMjUuMzU4MyA0MCAyNS44MzY4IDQwIDI2LjMzNTlDNDAgMjYuODM1IDM5LjgwMSAyNy4zMTM1IDM5LjQ0NzEgMjcuNjY1NUwzMi40MTU5IDM0LjY1NzZDMzEuNjgxNiAzNS4zODc4IDMwLjQ5NDQgMzUuMzg0NSAyOS43NjQyIDM0LjY1MDNaIiBmaWxsPSIjMzUzRjZFIi8+Cjwvc3ZnPgo="},"displayName":"Extract from File","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":1236,"icon":"file:aggregate.svg","name":"n8n-nodes-base.aggregate","codex":{"data":{"alias":["Aggregate","Combine","Flatten","Transform","Array","List","Item"],"details":"","resources":{"generic":[],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.aggregate/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Aggregate"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJub25lIj48ZyBmaWxsPSIjRkY2RDVBIiBjbGlwLXBhdGg9InVybCgjYSkiPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTMyIDE0OGMwLTYuNjI3IDUuMzczLTEyIDEyLTEyaDE0NmM2LjYyNyAwIDEyIDUuMzczIDEyIDEydjI0YzAgNi42MjctNS4zNzMgMTItMTIgMTJINDRjLTYuNjI3IDAtMTItNS4zNzMtMTItMTJ6bTAgOTZjMC02LjYyNyA1LjM3My0xMiAxMi0xMmgxNDZjNi42MjcgMCAxMiA1LjM3MyAxMiAxMnYyNGMwIDYuNjI3LTUuMzczIDEyLTEyIDEySDQ0Yy02LjYyNyAwLTEyLTUuMzczLTEyLTEyem0wIDk2YzAtNi42MjcgNS4zNzMtMTIgMTItMTJoMTQ2YzYuNjI3IDAgMTIgNS4zNzMgMTIgMTJ2MjRjMCA2LjYyNy01LjM3MyAxMi0xMiAxMkg0NGMtNi42MjcgMC0xMi01LjM3My0xMi0xMnoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGQ9Ik03NCA3NmMwIDYuNjI3IDUuMzczIDEyIDEyIDEyaDExNi4yMTdjMTcuNjczIDAgMzIgMTQuMzI3IDMyIDMydjU2YzAgMjYuOTc4IDEwLjI3MiA1MS41NTcgMjcuMTE5IDcwLjAzOSA1LjA1NSA1LjU0NSA1LjA1NSAxNC4zNzcgMCAxOS45MjItMTYuODQ3IDE4LjQ4Mi0yNy4xMTkgNDMuMDYxLTI3LjExOSA3MC4wMzl2NTZjMCAxNy42NzMtMTQuMzI3IDMyLTMyIDMySDg2Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0YzAgNi42MjcgNS4zNzMgMTIgMTIgMTJoMTE2LjIxN2M0NC4xODMgMCA4MC0zNS44MTcgODAtODB2LTU2YzAtMzAuOTI4IDI1LjA3Mi01NiA1Ni01NmE1Ljc4MyA1Ljc4MyAwIDAgMCA1Ljc4My01Ljc4M3YtMzYuNDM0YTUuNzgzIDUuNzgzIDAgMCAwLTUuNzgzLTUuNzgzYy0zMC45MjggMC01Ni0yNS4wNzItNTYtNTZ2LTU2YzAtNDQuMTgzLTM1LjgxNy04MC04MC04MEg4NmMtNi42MjcgMC0xMiA1LjM3My0xMiAxMnoiLz48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0zNzYgMjQ0YzAtNi42MjcgNS4zNzMtMTIgMTItMTJoMTEyYzYuNjI3IDAgMTIgNS4zNzMgMTIgMTJ2MjRjMCA2LjYyNy01LjM3MyAxMi0xMiAxMkgzODhjLTYuNjI3IDAtMTItNS4zNzMtMTItMTJ6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L2c+PGRlZnM+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz48L2NsaXBQYXRoPjwvZGVmcz48L3N2Zz4="},"displayName":"Aggregate","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]}],"categories":[{"id":35,"name":"Document Extraction"},{"id":49,"name":"AI Summarization"}],"image":[]}}