This workflow corresponds to n8n.io template #15894 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "gv2PH2ZULEQz5JCC",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "InstaPitch \u2014 Multi-Agent Consulting Proposal Automator",
"tags": [],
"nodes": [
{
"id": "2472624e-e759-4fac-8962-9b362fc45e59",
"name": "TigerPitch - Receive Inputs",
"type": "n8n-nodes-base.webhook",
"position": [
-800,
-400
],
"parameters": {
"path": "tigerpitch-generate",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "749f92f2-4f5c-481e-8040-94c3cf91eeb1",
"name": "Fetch Company Website",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"maxTries": 2,
"position": [
-224,
-384
],
"parameters": {
"url": "={{ \"https://r.jina.ai/\" + $json.body.company_url }}",
"options": {
"timeout": 30000,
"allowUnauthorizedCerts": true
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "text/plain"
}
]
}
},
"retryOnFail": true,
"typeVersion": 4.4,
"waitBetweenTries": 3000
},
{
"id": "e1231dd0-88bf-40e7-8fb5-186b18e8b862",
"name": "Website Content Handler",
"type": "n8n-nodes-base.code",
"position": [
0,
-384
],
"parameters": {
"jsCode": "const inputData =\n $('TigerPitch - Receive Inputs').item.json;\n\nlet websiteContent = '';\nlet fetchStatus = '';\n\ntry {\n\n const websiteFetch =\n $('Fetch Company Website').item;\n\n const raw =\n websiteFetch.json?.data ||\n websiteFetch.json?.body ||\n websiteFetch.json ||\n '';\n\n const text =\n typeof raw === 'string'\n ? raw\n : JSON.stringify(raw);\n\n if (text && text.length > 100) {\n\n websiteContent =\n text.substring(0, 3000);\n\n fetchStatus = 'website_success';\n\n } else {\n\n throw new Error('Empty website content');\n }\n\n} catch (e1) {\n\n try {\n\n const searchFetch =\n $('Search Company Fallback').item;\n\n const raw =\n searchFetch.json?.data ||\n searchFetch.json?.body ||\n searchFetch.json ||\n '';\n\n const text =\n typeof raw === 'string'\n ? raw\n : JSON.stringify(raw);\n\n if (text && text.length > 100) {\n\n websiteContent =\n text.substring(0, 3000);\n\n fetchStatus = 'search_success';\n\n } else {\n\n throw new Error('Empty search content');\n }\n\n } catch (e2) {\n\n fetchStatus = 'fallback_knowledge';\n\n websiteContent =\n `Company: ${inputData.company_name}.\n Industry: ${inputData.industry}.\n Website and search unavailable.\n Use your training knowledge about this company and industry.`;\n }\n}\n\nreturn [\n {\n json: {\n ...inputData,\n\n website_content: websiteContent,\n\n fetch_status: fetchStatus,\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "00bbaed5-2795-4d06-95ce-ab553f2495d2",
"name": "Extract Research Data",
"type": "n8n-nodes-base.code",
"position": [
704,
-384
],
"parameters": {
"jsCode": "let researchData = {};\n\ntry {\n // Anthropic node returns: content[0].text\n const rawText = $input.first().json\n ?.content?.[0]?.text || '{}';\n\n // Clean any backticks just in case\n let cleaned = rawText\n .replace(/```json/gi, '')\n .replace(/```/g, '')\n .trim();\n\n // Extract between first { and last }\n const firstBrace = cleaned.indexOf('{');\n const lastBrace = cleaned.lastIndexOf('}');\n\n if (firstBrace !== -1 && lastBrace !== -1) {\n cleaned = cleaned.substring(firstBrace, lastBrace + 1);\n researchData = JSON.parse(cleaned);\n } else {\n throw new Error('No JSON found');\n }\n\n} catch (e) {\n researchData = {\n company_overview: 'Research unavailable',\n products_services: 'Information unavailable',\n company_size: 'Unknown',\n recent_initiatives: 'No recent initiatives identified',\n technology_mentioned: 'No technology information available',\n visible_pain_points: 'Business pain points could not be identified',\n data_source: 'fallback'\n };\n}\n\nconst webhookData = $('TigerPitch - Receive Inputs').first().json;\nconst handlerData = $('Website Content Handler').first().json;\n\nreturn {\n ...webhookData,\n ...handlerData,\n research_data: researchData,\n};"
},
"typeVersion": 2
},
{
"id": "458ad887-0723-4379-87b0-36553dbd47a2",
"name": "Extract Industry Data",
"type": "n8n-nodes-base.code",
"position": [
1360,
-384
],
"parameters": {
"jsCode": "let industryData = {};\n\ntry {\n // Anthropic node returns: content[0].text\n const rawText = $input.first().json\n ?.content?.[0]?.text || '{}';\n\n let cleaned = rawText\n .replace(/```json/gi, '')\n .replace(/```/g, '')\n .trim();\n\n const firstBrace = cleaned.indexOf('{');\n const lastBrace = cleaned.lastIndexOf('}');\n\n if (firstBrace !== -1 && lastBrace !== -1) {\n cleaned = cleaned.substring(firstBrace, lastBrace + 1);\n industryData = JSON.parse(cleaned);\n } else {\n throw new Error('No JSON found');\n }\n\n} catch (e) {\n industryData = {\n industry_trends: 'Industry trends unavailable',\n common_root_causes: 'Root cause analysis unavailable',\n benchmark_data: 'Benchmark data unavailable',\n decision_maker_priorities: 'Decision maker priorities unavailable',\n ai_opportunities: 'AI opportunity analysis unavailable',\n competitive_context: 'Competitive context unavailable'\n };\n}\n\nconst prev = $('Extract Research Data').first().json;\n\nreturn {\n ...prev,\n industry_data: industryData,\n};"
},
"typeVersion": 2
},
{
"id": "3ebca7d5-48ac-422b-9d96-dc8f8cfe86c3",
"name": "Solution Router",
"type": "n8n-nodes-base.if",
"position": [
2352,
-352
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "87688609-e3ec-47a6-b845-b5efc89a7e79",
"operator": {
"type": "string",
"operation": "equals",
"singleValue": true
},
"leftValue": "={{ $('TigerPitch - Receive Inputs').item.json.body.solution_preference }}",
"rightValue": "ai_suggest"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "9557a5e3-4cf2-44c7-9b8b-dacb807a3238",
"name": "Solution Refiner - Gemini",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
2944,
112
],
"parameters": {
"url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent",
"method": "POST",
"options": {},
"jsonBody": "={\n \"contents\": [{\n \"parts\": [{\n \"text\": \"You are a senior consulting strategist.\\n\\nThe user has their own solution idea. Professionally refine, structure and elevate it into a clear consulting solution approach.\\n\\nUSER'S SOLUTION IDEA:\\n{{ $json.own_solution }}\\n\\nCOMPANY RESEARCH:\\n{{ JSON.stringify($json.research_data) }}\\n\\nINDUSTRY CONTEXT:\\n{{ JSON.stringify($json.industry_data) }}\\n\\nCLIENT PROBLEM: {{ $json.problem_statement }}\\nBUDGET: {{ $json.budget_range }}\\nTIMELINE: {{ $json.timeline }}\\nDECISION MAKER: {{ $json.decision_maker }}\\n\\nINSTRUCTIONS:\\n- Keep the user's core idea intact\\n- Improve clarity, structure and professional language\\n- Add specific phases and deliverables\\n- Make it relevant to the client's research data\\n\\nReturn ONLY a valid JSON object with no markdown, no explanation:\\n{\\n \\\"option_number\\\": 1,\\n \\\"title\\\": \\\"professional solution title derived from user's idea\\\",\\n \\\"description\\\": \\\"2 clear professional sentences describing the solution\\\",\\n \\\"approach\\\": \\\"structured 3-phase approach based on user's idea\\\",\\n \\\"key_deliverables\\\": \\\"3 specific deliverables the client will receive\\\",\\n \\\"best_for\\\": \\\"which type of client or situation this is best suited for\\\",\\n \\\"estimated_effort\\\": \\\"medium\\\",\\n \\\"expected_outcome\\\": \\\"specific measurable result the client can expect\\\"\\n}\"\n }]\n }],\n \"generationConfig\": {\n \"temperature\": 0.4,\n \"maxOutputTokens\": 1000,\n \"responseMimeType\": \"application/json\"\n }\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpQueryAuth"
},
"credentials": {
"httpQueryAuth": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.4,
"waitBetweenTries": 2000
},
{
"id": "fdd0b6eb-0f03-4a57-969c-f60fffd628de",
"name": "Extract Solutions",
"type": "n8n-nodes-base.code",
"position": [
3152,
-352
],
"parameters": {
"jsCode": "let solutions = [];\nconst prev = $('Knowledge Base Lookup').first().json;\n\ntry {\n const rawText = $input.first().json\n ?.content?.[0]?.text || '';\n\n if (!rawText) throw new Error('Empty');\n\n let cleaned = rawText\n .replace(/[\\u0000-\\u001F\\u007F-\\u009F]/g, ' ')\n .replace(/```json/gi, '')\n .replace(/```/g, '')\n .trim();\n\n // Find array or object\n const firstBracket = cleaned.indexOf('[');\n const lastBracket = cleaned.lastIndexOf(']');\n const firstBrace = cleaned.indexOf('{');\n const lastBrace = cleaned.lastIndexOf('}');\n\n if (firstBracket !== -1 && lastBracket !== -1) {\n // Normal array response\n const jsonStr = cleaned.substring(firstBracket, lastBracket + 1);\n solutions = JSON.parse(jsonStr);\n } else if (firstBrace !== -1 && lastBrace !== -1) {\n // Single object \u2014 wrap in array\n const jsonStr = cleaned.substring(firstBrace, lastBrace + 1);\n const parsed = JSON.parse(jsonStr);\n // Check if it has solutions array inside\n if (parsed.solutions && Array.isArray(parsed.solutions)) {\n solutions = parsed.solutions;\n } else {\n solutions = [parsed];\n }\n } else {\n throw new Error('No JSON found');\n }\n\n // Add option numbers if missing\n solutions = solutions.map((s, i) => ({\n ...s,\n option_number: s.option_number || (i + 1)\n }));\n\n // Validate we have 3\n if (!Array.isArray(solutions) || solutions.length === 0) {\n throw new Error('Empty solutions');\n }\n\n} catch (e) {\n solutions = [\n {\n option_number: 1,\n title: 'Predictive Analytics Solution',\n description: 'Build a predictive model to identify and address the core business problem proactively.',\n approach: 'Phase 1: Data audit \u2192 Phase 2: Model build \u2192 Phase 3: Deploy',\n key_deliverables: 'Predictive model, Dashboard, Playbook',\n best_for: 'Organizations with good data infrastructure',\n estimated_effort: 'medium',\n expected_outcome: '20-30% improvement within 3 months'\n },\n {\n option_number: 2,\n title: 'Data Intelligence Platform',\n description: 'Create a unified analytics platform delivering real-time actionable insights.',\n approach: 'Phase 1: Data consolidation \u2192 Phase 2: Analytics layer \u2192 Phase 3: Training',\n key_deliverables: 'Data platform, Dashboards, Training',\n best_for: 'Organizations needing better data visibility',\n estimated_effort: 'high',\n expected_outcome: 'Unified view of all key business metrics'\n },\n {\n option_number: 3,\n title: 'Quick Win POC',\n description: 'Fast 4-week proof of concept demonstrating immediate measurable value.',\n approach: 'Phase 1: Scoping \u2192 Phase 2: Prototype \u2192 Phase 3: Roadmap',\n key_deliverables: 'Working prototype, ROI analysis, Roadmap',\n best_for: 'Organizations wanting to validate before full investment',\n estimated_effort: 'low',\n expected_outcome: 'Validated approach with clear ROI projection'\n }\n ];\n}\n\nreturn {\n ...prev,\n solutions: solutions,\n status: 'solutions_ready'\n};"
},
"typeVersion": 2
},
{
"id": "d73eaa77-710e-4da6-8edb-87bb4369617b",
"name": "Extract Refined Solution",
"type": "n8n-nodes-base.code",
"position": [
3152,
112
],
"parameters": {
"jsCode": "let refinedSolution = {};\nconst prev = $('Knowledge Base Lookup').first().json;\n\ntry {\n const rawText = $input.first().json\n ?.content?.[0]?.text || '';\n\n let cleaned = rawText\n .replace(/[\\u0000-\\u001F\\u007F-\\u009F]/g, ' ')\n .replace(/```json/gi, '')\n .replace(/```/g, '')\n .trim();\n\n const firstBrace = cleaned.indexOf('{');\n const lastBrace = cleaned.lastIndexOf('}');\n\n if (firstBrace !== -1 && lastBrace !== -1) {\n refinedSolution = JSON.parse(\n cleaned.substring(firstBrace, lastBrace + 1)\n );\n } else {\n throw new Error('No JSON found');\n }\n\n} catch(e) {\n refinedSolution = {\n option_number: 1,\n title: 'Custom Solution Approach',\n description: prev.body?.own_solution || 'User defined solution',\n approach: 'Phase 1: Discovery \u2192 Phase 2: Build \u2192 Phase 3: Deploy',\n key_deliverables: 'Custom solution, Documentation, Training',\n best_for: 'Tailored to specific client needs',\n estimated_effort: 'medium',\n expected_outcome: 'Measurable improvement in identified problem area'\n };\n}\n\nconst response = {\n status: \"own_solution_ready\",\n company_name: prev.body?.company_name || prev.company_name || '',\n industry: prev.body?.industry || prev.industry || '',\n decision_maker: prev.body?.decision_maker || prev.decision_maker || '',\n problem_statement: prev.body?.problem_statement || prev.problem_statement || '',\n business_impact: prev.body?.business_impact || prev.business_impact || '',\n technologies: prev.body?.technologies || prev.technologies || [],\n delivery_type: prev.body?.delivery_type || prev.delivery_type || '',\n timeline: prev.body?.timeline || prev.timeline || '',\n budget_range: prev.body?.budget_range || prev.budget_range || '',\n selected_solution: refinedSolution,\n solutions: [refinedSolution],\n research_data: prev.research_data || {},\n industry_data: prev.industry_data || {},\n kb_context: prev.kb_context || '',\n has_kb: prev.has_kb || false\n};\n\nreturn { response: JSON.stringify(response) };"
},
"typeVersion": 2
},
{
"id": "a7bdb133-e361-4036-80ce-3a3794bb1afd",
"name": "Return Solutions to Frontend",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
3616,
-304
],
"parameters": {
"options": {
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"respondWith": "text",
"responseBody": "=={{ $json.response }}"
},
"retryOnFail": true,
"typeVersion": 1.5
},
{
"id": "417b295e-edca-4a7a-996c-9f53eca30bca",
"name": "Receive Selected Solution",
"type": "n8n-nodes-base.webhook",
"position": [
-816,
224
],
"parameters": {
"path": "tigerpitch-solution",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "9901d78e-7e58-4f19-81be-ba9d450ebd35",
"name": "Extract Pain Map",
"type": "n8n-nodes-base.code",
"position": [
-240,
224
],
"parameters": {
"jsCode": "let painMap = {};\n\ntry {\n const rawText = $input.first().json\n ?.content?.[0]?.text || '{}';\n\n let cleaned = rawText\n .replace(/```json/gi, '')\n .replace(/```/g, '')\n .trim();\n\n const firstBrace = cleaned.indexOf('{');\n const lastBrace = cleaned.lastIndexOf('}');\n\n if (firstBrace !== -1 && lastBrace !== -1) {\n cleaned = cleaned.substring(firstBrace, lastBrace + 1);\n painMap = JSON.parse(cleaned);\n } else {\n throw new Error('No JSON found');\n }\n\n if (!painMap.pain_points) {\n throw new Error('Invalid pain map structure');\n }\n\n} catch (e) {\n const prev = $('Receive Selected Solution').first().json;\n\n painMap = {\n pain_points: [\n {\n pain: prev.body?.problem_statement || 'Business challenge identified',\n root_cause: 'Limited visibility into operational and customer intelligence',\n solution_component: prev.body?.selected_solution?.title || 'AI-driven analytics solution',\n expected_outcome: 'Improved decision-making and operational efficiency',\n timeframe: prev.body?.timeline || '3 months'\n }\n ],\n overall_value_statement: 'AI-driven modernization designed to improve business performance.',\n roi_indicator: 'Expected measurable ROI within 6-12 months'\n };\n}\n\nconst prev = $('Receive Selected Solution').first().json;\n\nreturn {\n ...prev,\n pain_map: painMap\n};"
},
"typeVersion": 2
},
{
"id": "a73377a3-18d7-4323-abbf-67265b750156",
"name": "PDF Formatter",
"type": "n8n-nodes-base.code",
"position": [
640,
368
],
"parameters": {
"jsCode": "const data = $input.first().json || {};\nconst rawProposal = data.proposal || {};\n\n// Map all 11 new sections\nconst p = {\n engagement_title: rawProposal.engagement_title || 'AI-Powered Analytics Program',\n situation_assessment: rawProposal.situation_assessment || 'Not available',\n root_cause_diagnosis: rawProposal.root_cause_diagnosis || 'Not available',\n tiger_pov: rawProposal.tiger_pov || 'Not available',\n proposed_solution: rawProposal.proposed_solution || 'Not available',\n pain_solution_map: rawProposal.pain_solution_map || 'Not available',\n tiger_accelerators: rawProposal.tiger_accelerators || 'Not available',\n investment_roi: rawProposal.investment_roi || 'Not available',\n risk_mitigation: rawProposal.risk_mitigation || 'Not available',\n our_team: rawProposal.our_team || 'Not available',\n credentials: rawProposal.credentials || 'Not available',\n next_steps: rawProposal.next_steps || 'Not available',\n};\n\nconst company = data.body?.company_name || data.company_name || 'Unknown Company';\nconst decisionMaker= data.body?.decision_maker || data.decision_maker || 'Decision Maker';\nconst industry = data.body?.industry || data.industry || '';\nconst budget = data.body?.budget_range || data.budget_range || '';\nconst timeline = data.body?.timeline || data.timeline || '';\nconst delivery = data.body?.delivery_type || data.delivery_type || '';\nconst hasKB = data.body?.has_kb || data.has_kb || false;\nconst kbIndustry = data.body?.kb_industry || data.kb_industry || '';\n\nconst date = new Date().toLocaleDateString('en-IN', {\n year: 'numeric', month: 'long', day: 'numeric'\n});\n\n// Safe string converter\nconst safe = (val) => {\n if (val === null || val === undefined) return 'Not available';\n if (Array.isArray(val)) return val.join(', ');\n if (typeof val === 'object') return JSON.stringify(val);\n return String(val).replace(/\\n/g, ' ');\n};\n\n// Parse || separated items into array\nconst parseItems = (text) => {\n if (!text || text === 'Not available') return [];\n return text.split('||').map(s => s.trim()).filter(Boolean);\n};\n\n// Parse root causes (CAUSE 1: title \u2014 desc || CAUSE 2...)\nconst renderRootCauses = (text) => {\n const items = parseItems(text);\n if (!items.length) return `<div class=\"section-box\">${safe(text)}</div>`;\n\n const colors = ['#DC2626','#B45309','#1A3C6E'];\n const bgColors = ['#FEE2E2','#FEF3C7','#EEF4FB'];\n\n return items.map((item, i) => {\n const parts = item.split('\u2014').map(s => s.trim());\n const title = parts[0].replace(/^CAUSE\\s*\\d+:\\s*/i, '').trim();\n const desc = parts[1] || '';\n const rest = parts.slice(2).join('\u2014').trim();\n return `\n <div style=\"display:flex;gap:14px;padding:14px 16px;background:${bgColors[i]||'#F5F5F5'};border-radius:8px;margin-bottom:10px;border:1px solid ${colors[i]||'#ccc'}20;\">\n <div style=\"width:28px;height:28px;border-radius:50%;background:${colors[i]||'#888'};color:white;font-weight:700;font-size:12px;display:flex;align-items:center;justify-content:center;flex-shrink:0;\">${i+1}</div>\n <div>\n <div style=\"font-weight:700;font-size:13px;color:#1A1A1A;margin-bottom:4px;\">${title}</div>\n ${desc ? `<div style=\"font-size:13px;color:#555;line-height:1.6;\">${desc}</div>` : ''}\n ${rest ? `<div style=\"margin-top:6px;display:inline-block;padding:2px 10px;background:${colors[i]||'#888'}20;border-radius:4px;font-size:11px;font-weight:700;color:${colors[i]||'#888'};\">${rest}</div>` : ''}\n </div>\n </div>`;\n }).join('');\n};\n\n// Parse phases (PHASE 1 (Wk X-X): what | Team: who | Outcome: result)\nconst renderPhases = (text) => {\n const items = parseItems(text);\n if (!items.length) return `<div class=\"section-box\">${safe(text)}</div>`;\n\n const phColors = ['#1A3C6E','#E07B2A','#16A34A','#7C3AED'];\n\n return `\n <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">\n <thead>\n <tr>\n ${['Phase','Deliverables','Team','Timeline','Outcome'].map(h =>\n `<th style=\"background:#1A3C6E;color:white;padding:10px 12px;text-align:left;font-size:12px;\">${h}</th>`\n ).join('')}\n </tr>\n </thead>\n <tbody>\n ${items.map((item, i) => {\n const phaseMatch = item.match(/PHASE\\s*(\\d+)[^:]*:\\s*(.*?)(?=\\|Team:|$)/i);\n const teamMatch = item.match(/Team:\\s*(.*?)(?=\\|Outcome:|$)/i);\n const outcomeMatch = item.match(/Outcome:\\s*(.*?)$/i);\n const weekMatch = item.match(/\\(Wk[s]?\\s*([\\d\\-]+)\\)/i);\n\n const phaseNum = phaseMatch?.[1] || (i+1).toString();\n const phaseDesc = phaseMatch?.[2]?.trim() || item;\n const team = teamMatch?.[1]?.trim() || 'Tiger Analytics Team';\n const outcome = outcomeMatch?.[1]?.trim()|| '';\n const weeks = weekMatch?.[1] ? `Wk ${weekMatch[1]}` : '';\n const bg = i % 2 === 0 ? '#F8FAFC' : '#FFFFFF';\n\n return `\n <tr style=\"background:${bg};\">\n <td style=\"padding:11px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;\">\n <span style=\"width:24px;height:24px;border-radius:50%;background:${phColors[i]||'#888'};color:white;font-weight:700;font-size:11px;display:inline-flex;align-items:center;justify-content:center;margin-right:8px;\">${phaseNum}</span>\n </td>\n <td style=\"padding:11px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;font-size:13px;\">${phaseDesc}</td>\n <td style=\"padding:11px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;font-size:12px;color:#555;\">${team}</td>\n <td style=\"padding:11px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;\"><span style=\"background:#EEF4FB;color:#1A3C6E;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;\">${weeks}</span></td>\n <td style=\"padding:11px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;font-size:12px;color:#16A34A;font-weight:600;\">${outcome}</td>\n </tr>`;\n }).join('')}\n </tbody>\n </table>`;\n};\n\n// Parse pain solution map\nconst renderPainMap = (text) => {\n const items = parseItems(text);\n if (!items.length) return `<div class=\"section-box\">${safe(text)}</div>`;\n\n const sevColors = { critical:'#DC2626', high:'#B45309', medium:'#1A3C6E' };\n const sevBg = { critical:'#FEE2E2', high:'#FEF3C7', medium:'#EEF4FB' };\n\n return `\n <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">\n <thead>\n <tr>\n ${['Pain Point','Severity','Solution','Outcome','By'].map(h =>\n `<th style=\"background:#1A3C6E;color:white;padding:10px 12px;text-align:left;font-size:12px;\">${h}</th>`\n ).join('')}\n </tr>\n </thead>\n <tbody>\n ${items.map((item, i) => {\n const painMatch = item.match(/PAIN:\\s*(.*?)(?=\\|SEVERITY:|$)/i);\n const sevMatch = item.match(/SEVERITY:\\s*(.*?)(?=\\|ROOT CAUSE:|$)/i);\n const solMatch = item.match(/SOLUTION:\\s*(.*?)(?=\\|OUTCOME:|$)/i);\n const outcomeMatch = item.match(/OUTCOME:\\s*(.*?)(?=\\|BY:|$)/i);\n const byMatch = item.match(/BY:\\s*(.*?)$/i);\n\n const pain = painMatch?.[1]?.trim() || item;\n const sev = (sevMatch?.[1]?.trim() || 'High').toLowerCase();\n const sol = solMatch?.[1]?.trim() || '';\n const outcome = outcomeMatch?.[1]?.trim() || '';\n const by = byMatch?.[1]?.trim() || '';\n const bg = i % 2 === 0 ? '#F8FAFC' : '#FFFFFF';\n const color = sevColors[sev] || sevColors.high;\n const bgSev = sevBg[sev] || sevBg.high;\n\n return `\n <tr style=\"background:${bg};\">\n <td style=\"padding:10px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;font-size:13px;\">\n <span style=\"display:inline-block;padding:2px 7px;border-radius:4px;font-size:10px;font-weight:700;background:${bgSev};color:${color};margin-bottom:4px;\">${sev.toUpperCase()}</span><br>${pain}\n </td>\n <td style=\"padding:10px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;\"><span style=\"padding:3px 9px;border-radius:4px;background:${bgSev};color:${color};font-size:11px;font-weight:700;\">${sev.charAt(0).toUpperCase()+sev.slice(1)}</span></td>\n <td style=\"padding:10px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;font-size:13px;\">${sol}</td>\n <td style=\"padding:10px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;font-size:12px;color:#16A34A;font-weight:600;\">${outcome}</td>\n <td style=\"padding:10px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;\"><span style=\"background:#EEF4FB;color:#1A3C6E;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;\">${by}</span></td>\n </tr>`;\n }).join('')}\n </tbody>\n </table>`;\n};\n\n// Parse accelerators\nconst renderAccelerators = (text) => {\n const items = parseItems(text);\n if (!items.length) return `<div class=\"section-box\">${safe(text)}</div>`;\n\n return `<div style=\"display:grid;grid-template-columns:repeat(3,1fr);gap:12px;\">\n ${items.map(item => {\n const nameMatch = item.match(/NAME:\\s*(.*?)(?=\\|WHAT:|$)/i);\n const whatMatch = item.match(/WHAT:\\s*(.*?)(?=\\|SAVES:|$)/i);\n const savesMatch = item.match(/SAVES:\\s*(.*?)$/i);\n const name = nameMatch?.[1]?.trim() || item;\n const what = whatMatch?.[1]?.trim() || '';\n const saves = savesMatch?.[1]?.trim() || '';\n return `\n <div style=\"border:1.5px solid #E2E8F0;border-radius:10px;padding:14px;\">\n <div style=\"font-weight:700;font-size:13px;color:#1A3C6E;margin-bottom:6px;\">\u26a1 ${name}</div>\n <div style=\"font-size:12px;color:#555;line-height:1.5;margin-bottom:8px;\">${what}</div>\n ${saves ? `<span style=\"background:#DCFCE7;color:#16A34A;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;\">Saves ${saves}</span>` : ''}\n </div>`;\n }).join('')}\n </div>`;\n};\n\n// Parse investment ROI\nconst renderROI = (text) => {\n if (!text || text === 'Not available') return `<div class=\"section-box\">${safe(text)}</div>`;\n\n const parts = text.split('||').map(s => s.trim());\n const phasePart = parts[0] || '';\n const roiPart = parts[1] || '';\n\n const phaseRows = phasePart.split('|').map(s => s.trim()).filter(Boolean);\n const roiItems = roiPart.split('|').map(s => s.trim()).filter(Boolean);\n\n return `\n <div style=\"display:grid;grid-template-columns:1fr 1fr;gap:16px;\">\n <div>\n <div style=\"font-size:12px;font-weight:700;color:#1A3C6E;margin-bottom:10px;text-transform:uppercase;letter-spacing:.5px;\">Phase Investment</div>\n <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">\n ${phaseRows.map((row, i) => {\n const [label, val] = row.split(':').map(s => s.trim());\n const isTotal = label?.toLowerCase().includes('total');\n return `<tr style=\"background:${isTotal ? '#EEF4FB' : i%2===0?'#F8FAFC':'#FFF'};\">\n <td style=\"padding:9px 12px;border-bottom:1px solid #E2E8F0;${isTotal?'font-weight:700;color:#1A3C6E;':''}\">${label||''}</td>\n <td style=\"padding:9px 12px;border-bottom:1px solid #E2E8F0;text-align:right;font-weight:700;${isTotal?'color:#1A3C6E;':''}\">${val||''}</td>\n </tr>`;\n }).join('')}\n </table>\n </div>\n <div>\n <div style=\"font-size:12px;font-weight:700;color:#1A3C6E;margin-bottom:10px;text-transform:uppercase;letter-spacing:.5px;\">Return on Investment</div>\n ${roiItems.map(item => {\n const [label, val] = item.split(':').map(s => s.trim());\n const isPayback = label?.toLowerCase().includes('payback');\n const isRisk = label?.toLowerCase().includes('risk') || label?.toLowerCase().includes('loss');\n const isROI = label?.toLowerCase().includes('roi');\n const bg = isPayback ? '#1A3C6E' : isRisk ? '#FEE2E2' : '#DCFCE7';\n const color = isPayback ? 'white' : isRisk ? '#DC2626' : '#16A34A';\n const textColor = isPayback ? 'rgba(255,255,255,.8)' : '#555';\n return `\n <div style=\"display:flex;justify-content:space-between;align-items:center;padding:9px 14px;border-radius:7px;background:${bg};margin-bottom:6px;\">\n <span style=\"font-size:13px;color:${textColor};\">${label||''}</span>\n <span style=\"font-weight:700;font-size:14px;color:${color};\">${val||''}</span>\n </div>`;\n }).join('')}\n </div>\n </div>`;\n};\n\n// Parse risk mitigation\nconst renderRisks = (text) => {\n const items = parseItems(text);\n if (!items.length) return `<div class=\"section-box\">${safe(text)}</div>`;\n\n const likColors = { high:'#DC2626', medium:'#B45309', low:'#16A34A' };\n const likBg = { high:'#FEE2E2', medium:'#FEF3C7', low:'#DCFCE7' };\n\n return `\n <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">\n <thead>\n <tr>\n ${['Risk','Likelihood','Tiger Mitigation','Protection'].map(h =>\n `<th style=\"background:#F8FAFC;color:#1A1A1A;padding:10px 12px;text-align:left;font-size:12px;font-weight:700;border-bottom:2px solid #E2E8F0;\">${h}</th>`\n ).join('')}\n </tr>\n </thead>\n <tbody>\n ${items.map((item, i) => {\n const riskMatch = item.match(/RISK:\\s*(.*?)(?=\\|LIKELIHOOD:|$)/i);\n const likeMatch = item.match(/LIKELIHOOD:\\s*(.*?)(?=\\|MITIGATION:|$)/i);\n const mitMatch = item.match(/MITIGATION:\\s*(.*?)(?=\\|PROTECTION:|$)/i);\n const protMatch = item.match(/PROTECTION:\\s*(.*?)$/i);\n\n const risk = riskMatch?.[1]?.trim() || item;\n const like = (likeMatch?.[1]?.trim() || 'Medium').toLowerCase();\n const mit = mitMatch?.[1]?.trim() || '';\n const prot = protMatch?.[1]?.trim() || '';\n const color = likColors[like] || likColors.medium;\n const bg = likBg[like] || likBg.medium;\n const rowBg = i % 2 === 0 ? '#F8FAFC' : '#FFFFFF';\n\n return `\n <tr style=\"background:${rowBg};\">\n <td style=\"padding:10px 12px;border-bottom:1px solid #E2E8F0;font-weight:600;vertical-align:top;\">${risk}</td>\n <td style=\"padding:10px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;\"><span style=\"padding:3px 9px;border-radius:4px;background:${bg};color:${color};font-size:11px;font-weight:700;\">${like.charAt(0).toUpperCase()+like.slice(1)}</span></td>\n <td style=\"padding:10px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;font-size:13px;color:#555;\">${mit}</td>\n <td style=\"padding:10px 12px;border-bottom:1px solid #E2E8F0;vertical-align:top;font-size:12px;color:#16A34A;font-weight:600;\">${prot}</td>\n </tr>`;\n }).join('')}\n </tbody>\n </table>`;\n};\n\n// Parse team\nconst renderTeam = (text) => {\n const items = parseItems(text);\n if (!items.length) return `<div class=\"section-box\">${safe(text)}</div>`;\n\n const avatarColors = ['#1A3C6E','#E07B2A','#16A34A','#7C3AED'];\n\n return `<div style=\"display:grid;grid-template-columns:repeat(4,1fr);gap:12px;\">\n ${items.map((item, i) => {\n const roleMatch = item.match(/ROLE:\\s*(.*?)(?=\\|EXP:|$)/i);\n const expMatch = item.match(/EXP:\\s*(.*?)(?=\\|SKILL:|$)/i);\n const skillMatch = item.match(/SKILL:\\s*(.*?)$/i);\n const role = roleMatch?.[1]?.trim() || item;\n const exp = expMatch?.[1]?.trim() || '';\n const skill = skillMatch?.[1]?.trim() || '';\n const initials = role.split(' ').map(w=>w[0]).join('').substring(0,2).toUpperCase();\n const color = avatarColors[i] || '#1A3C6E';\n return `\n <div style=\"border:1px solid #E2E8F0;border-radius:10px;padding:14px;text-align:center;\">\n <div style=\"width:44px;height:44px;border-radius:50%;background:${color};color:white;font-weight:700;font-size:14px;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;\">${initials}</div>\n <div style=\"font-weight:700;font-size:12px;color:#1A3C6E;margin-bottom:4px;\">${role}</div>\n <div style=\"font-size:11px;color:#555;margin-bottom:8px;line-height:1.4;\">${exp}</div>\n ${skill ? `<span style=\"background:#EEF4FB;color:#1A3C6E;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600;\">${skill}</span>` : ''}\n </div>`;\n }).join('')}\n </div>`;\n};\n\n// Parse credentials\nconst renderCredentials = (text) => {\n const items = parseItems(text);\n if (!items.length) return `<div class=\"section-box\">${safe(text)}</div>`;\n\n const credColors = ['#FEF3C7','#EEF4FB','#EDE9FE','#DCFCE7'];\n const credIcons = ['\ud83c\udfc6','\u2601\ufe0f','\ud83d\udcca','\ud83c\udf0f'];\n\n return `<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:12px;\">\n ${items.map((item, i) => {\n const awardMatch = item.match(/(?:AWARD|CREDENTIAL):\\s*(.*?)(?=\\|RELEVANCE:|$)/i);\n const relMatch = item.match(/RELEVANCE:\\s*(.*?)$/i);\n const award = awardMatch?.[1]?.trim() || item;\n const rel = relMatch?.[1]?.trim() || '';\n return `\n <div style=\"background:${credColors[i]||'#F5F5F5'};border-radius:9px;padding:14px;\">\n <div style=\"font-size:20px;margin-bottom:6px;\">${credIcons[i]||'\u2b50'}</div>\n <div style=\"font-weight:700;font-size:13px;margin-bottom:4px;\">${award}</div>\n <div style=\"font-size:12px;color:#555;line-height:1.4;\">${rel}</div>\n </div>`;\n }).join('')}\n </div>`;\n};\n\n// Parse next steps timeline\nconst renderNextSteps = (text) => {\n const items = parseItems(text);\n if (!items.length) return `<div class=\"section-box\">${safe(text)}</div>`;\n\n return `<div style=\"display:flex;flex-direction:column;gap:0;\">\n ${items.map((item, i) => {\n const whenMatch = item.match(/WHEN:\\s*(.*?)(?=\\|(?:TITLE|ACTION):|$)/i);\n const titleMatch = item.match(/(?:TITLE|ACTION):\\s*(.*?)(?=\\|DESC(?:RIPTION)?:|$)/i);\n const descMatch = item.match(/DESC(?:RIPTION)?:\\s*(.*?)$/i);\n const when = whenMatch?.[1]?.trim() || '';\n const title = titleMatch?.[1]?.trim() || item;\n const desc = descMatch?.[1]?.trim() || '';\n const isLast = i === items.length - 1;\n return `\n <div style=\"display:flex;gap:0;\">\n <div style=\"display:flex;flex-direction:column;align-items:center;width:48px;flex-shrink:0;\">\n <div style=\"width:36px;height:36px;border-radius:50%;background:#1A3C6E;color:white;font-weight:700;font-size:13px;display:flex;align-items:center;justify-content:center;\">${i+1}</div>\n ${!isLast ? `<div style=\"width:2px;background:#E2E8F0;flex:1;margin:4px 0;min-height:20px;\"></div>` : ''}\n </div>\n <div style=\"padding:0 0 ${isLast?'0':'24px'} 16px;flex:1;\">\n ${when ? `<div style=\"font-size:11px;font-weight:700;color:#E07B2A;margin-bottom:3px;\">${when}</div>` : ''}\n <div style=\"font-weight:700;font-size:14px;color:#1A1A1A;margin-bottom:4px;\">${title}</div>\n ${desc ? `<div style=\"font-size:13px;color:#555;line-height:1.5;\">${desc}</div>` : ''}\n </div>\n </div>`;\n }).join('')}\n </div>`;\n};\n\n// Build complete HTML\nconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<title>Proposal \u2014 ${safe(company)}</title>\n<style>\n* { margin:0; padding:0; box-sizing:border-box; }\nbody { font-family:'Segoe UI',Arial,sans-serif; color:#1A1A1A; background:#F0F4FA; }\n.page { max-width:960px; margin:0 auto; background:white; }\n.header { background:linear-gradient(135deg,#0F2544,#1A3C6E); color:white; padding:40px 48px; position:relative; overflow:hidden; }\n.header::before { content:''; position:absolute; top:-60px; right:-60px; width:200px; height:200px; border-radius:50%; background:rgba(255,255,255,.05); }\n.header-tag { display:inline-block; background:rgba(255,255,255,.15); color:#F0C080; padding:4px 14px; border-radius:20px; font-size:12px; font-weight:700; margin-bottom:14px; }\n.header h1 { font-size:28px; margin-bottom:6px; }\n.header-sub { font-size:14px; opacity:.8; margin-bottom:20px; }\n.header-divider { width:60px; height:3px; background:#E07B2A; border-radius:2px; margin-bottom:16px; }\n.header-meta { display:flex; gap:24px; flex-wrap:wrap; font-size:13px; opacity:.85; }\n.header-meta strong { color:#F0C080; }\n.stats { display:grid; grid-template-columns:repeat(4,1fr); background:white; border-bottom:1px solid #E2E8F0; }\n.stat { padding:20px; text-align:center; border-right:1px solid #E2E8F0; }\n.stat:last-child { border-right:none; }\n.stat-icon { font-size:20px; margin-bottom:6px; }\n.stat-num { font-size:26px; font-weight:800; margin-bottom:3px; }\n.stat-label { font-size:11px; color:#64748B; }\n.content { padding:32px 48px; }\n.section-card { background:white; border-radius:12px; border:1px solid #E2E8F0; margin-bottom:20px; overflow:hidden; box-shadow:0 1px 3px rgba(0,0,0,.06); }\n.section-header { padding:14px 20px; border-bottom:1px solid #E2E8F0; background:#F8FAFC; display:flex; align-items:center; gap:10px; }\n.section-num { width:26px; height:26px; border-radius:50%; background:#1A3C6E; color:white; font-size:11px; font-weight:700; display:flex; align-items:center; justify-content:center; flex-shrink:0; }\n.section-title { font-size:14px; font-weight:700; color:#1A3C6E; }\n.section-body { padding:20px; }\n.section-box { font-size:14px; line-height:1.85; color:#1A1A1A; background:#EEF4FB; padding:16px 18px; border-radius:8px; border-left:4px solid #1A3C6E; }\n.pov-box { background:linear-gradient(135deg,#0F2544,#1A3C6E); border-radius:10px; padding:20px; color:white; }\n.pov-label { font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:1px; color:#F0C080; margin-bottom:8px; }\n.pov-text { font-size:14px; line-height:1.8; opacity:.95; }\n.kb-badge { display:inline-block; background:#EEF4FB; color:#1A3C6E; padding:3px 12px; border-radius:20px; font-size:11px; font-weight:700; margin-left:8px; }\n.footer { background:#0F2544; color:white; padding:20px 48px; display:flex; justify-content:space-between; font-size:12px; opacity:.9; }\n</style>\n</head>\n<body>\n<div class=\"page\">\n\n <!-- HEADER -->\n <div class=\"header\">\n <div class=\"header-tag\">\ud83d\udc2f Tiger Analytics \u00b7 Confidential Proposal</div>\n <h1>${safe(p.engagement_title)}</h1>\n <div class=\"header-divider\"></div>\n <div class=\"header-sub\">Prepared for ${safe(company)} \u00b7 ${safe(decisionMaker)} \u00b7 ${industry}</div>\n <div class=\"header-meta\">\n <span><strong>Date:</strong> ${date}</span>\n <span><strong>Budget:</strong> ${safe(budget)}</span>\n <span><strong>Timeline:</strong> ${safe(timeline)}</span>\n <span><strong>Delivery:</strong> ${safe(delivery)}</span>\n ${hasKB ? `<span><strong>KB:</strong> ${kbIndustry} Knowledge Base Active \u2713</span>` : ''}\n </div>\n </div>\n\n <!-- STAT CARDS -->\n <div class=\"stats\">\n <div class=\"stat\"><div class=\"stat-icon\">\ud83d\udcb8</div><div class=\"stat-num\" style=\"color:#DC2626;\">$12M</div><div class=\"stat-label\">Revenue at Risk</div></div>\n <div class=\"stat\"><div class=\"stat-icon\">\ud83d\udcc9</div><div class=\"stat-num\" style=\"color:#E07B2A;\">25-30%</div><div class=\"stat-label\">Target Improvement</div></div>\n <div class=\"stat\"><div class=\"stat-icon\">\u26a1</div><div class=\"stat-num\" style=\"color:#16A34A;\">4-6 wks</div><div class=\"stat-label\">Payback Period</div></div>\n <div class=\"stat\"><div class=\"stat-icon\">\ud83d\udcc5</div><div class=\"stat-num\" style=\"color:#1A3C6E;\">${safe(timeline)}</div><div class=\"stat-label\">Engagement Timeline</div></div>\n </div>\n\n <div class=\"content\">\n\n <!-- 1. SITUATION -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">1</div><div class=\"section-title\">Situation Assessment</div></div>\n <div class=\"section-body\"><div class=\"section-box\">${safe(p.situation_assessment)}</div></div>\n </div>\n\n <!-- 2. ROOT CAUSE -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">2</div><div class=\"section-title\">Root Cause Diagnosis</div></div>\n <div class=\"section-body\">${renderRootCauses(p.root_cause_diagnosis)}</div>\n </div>\n\n <!-- 3. TIGER POV -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">3</div><div class=\"section-title\">Tiger Analytics Point of View</div></div>\n <div class=\"section-body\">\n <div class=\"pov-box\">\n <div class=\"pov-label\">\ud83d\udc2f Expert Perspective</div>\n <div class=\"pov-text\">${safe(p.tiger_pov)}</div>\n </div>\n </div>\n </div>\n\n <!-- 4. SOLUTION -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">4</div><div class=\"section-title\">Proposed Solution</div></div>\n <div class=\"section-body\">${renderPhases(p.proposed_solution)}</div>\n </div>\n\n <!-- 5. PAIN MAP -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">5</div><div class=\"section-title\">Pain Point \u2192 Solution Mapping</div></div>\n <div class=\"section-body\">${renderPainMap(p.pain_solution_map)}</div>\n </div>\n\n <!-- 6. ACCELERATORS -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">6</div><div class=\"section-title\">Tiger Analytics Accelerators</div></div>\n <div class=\"section-body\">${renderAccelerators(p.tiger_accelerators)}</div>\n </div>\n\n <!-- 7. INVESTMENT -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">7</div><div class=\"section-title\">Investment & ROI</div></div>\n <div class=\"section-body\">${renderROI(p.investment_roi)}</div>\n </div>\n\n <!-- 8. RISK -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">8</div><div class=\"section-title\">Risk Mitigation</div></div>\n <div class=\"section-body\">${renderRisks(p.risk_mitigation)}</div>\n </div>\n\n <!-- 9. TEAM -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">9</div><div class=\"section-title\">Our Team for This Engagement</div></div>\n <div class=\"section-body\">${renderTeam(p.our_team)}</div>\n </div>\n\n <!-- 10. CREDENTIALS -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">10</div><div class=\"section-title\">Tiger Analytics Credentials</div></div>\n <div class=\"section-body\">${renderCredentials(p.credentials)}</div>\n </div>\n\n <!-- 11. NEXT STEPS -->\n <div class=\"section-card\">\n <div class=\"section-header\"><div class=\"section-num\">11</div><div class=\"section-title\">Next Steps</div></div>\n <div class=\"section-body\">${renderNextSteps(p.next_steps)}</div>\n </div>\n\n </div>\n\n <!-- FOOTER -->\n <div class=\"footer\">\n <span>\ud83d\udc2f TigerPitch \u00b7 Tiger Analytics \u00b7 Confidential \u00b7 Not for External Distribution</span>\n <span>Generated: ${date} ${hasKB ? `\u00b7 ${kbIndustry} KB Active` : ''}</span>\n </div>\n\n</div>\n</body>\n</html>`;\n\nreturn [{\n json: {\n ...data,\n html_proposal: html,\n status: 'completed',\n generated_at: data.generated_at || new Date().toISOString(),\n }\n}];"
},
"typeVersion": 2
},
{
"id": "c0158c40-2d0f-4bbf-9dce-1d9b6da75ca7",
"name": "Return Final Proposal to Frontend",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1056,
368
],
"parameters": {
"options": {
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"respondWith": "text",
"responseBody": "={{ $json.response }}"
},
"retryOnFail": true,
"typeVersion": 1.5
},
{
"id": "97e81d34-03dd-43b9-bb69-106becee85fb",
"name": "Industry Agent",
"type": "@n8n/n8n-nodes-langchain.anthropic",
"position": [
1008,
-384
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gemini-2.5-flash",
"cachedResultName": "gemini-2.5-flash"
},
"options": {
"system": "You are a senior industry strategy analyst working for Tiger Analytics. Your responsibility is to analyze industry-specific business challenges and identify AI, analytics, and digital transformation opportunities relevant to enterprise consulting proposals. Guidelines: - Focus on enterprise business value - Provide practical and industry-relevant insights - Emphasize AI, analytics, automation, operational efficiency, customer experience, and decision intelligence - Tailor insights based on the decision maker role - Be concise, strategic, and professional - Never use markdown - Never explain reasoning - Return ONLY valid JSON"
},
"messages": {
"values": [
{
"content": "=Analyze the following business and industry context.\n\nINDUSTRY:\n{{ $json.body.industry }}\n\nPROBLEM STATEMENT:\n{{ $json.body.problem_statement }}\n\nBUSINESS IMPACT:\n{{ $json.body.business_impact }}\n\nDECISION MAKER:\n{{ $json.body.decision_maker }}\n\nCOMPANY RESEARCH:\n{{ JSON.stringify($json.research_data) }}\n\nReturn JSON in this exact structure:\n\n{\n \"industry_trends\": \"\",\n \"common_root_causes\": \"\",\n \"benchmark_data\": \"\",\n \"decision_maker_priorities\": \"\",\n \"ai_opportunities\": \"\",\n \"competitive_context\": \"\"\n}"
}
]
}
},
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "b0f712d2-0bbb-4606-b0de-69c863d37a89",
"name": "Research Agent",
"type": "@n8n/n8n-nodes-langchain.anthropic",
"position": [
384,
-384
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gemini-2.5-flash",
"cachedResultName": "gemini-2.5-flash"
},
"options": {
"system": "You are a senior business research analyst working for Tiger Analytics, a data and AI consulting company. Your responsibility is to analyze company and industry information and generate structured business intelligence for AI consulting proposals. Guidelines: - Always provide factual, business-oriented insights - Focus on AI, analytics, automation, customer experience, operational efficiency, and digital transformation opportunities - If website content is unavailable, use general industry and company knowledge - Be concise, specific, and professional - Never return markdown - Never explain your reasoning - Return ONLY valid JSON"
},
"messages": {
"values": [
{
"content": "=Analyze this company:\n\nDATA SOURCE STATUS:\n{{ $json.fetch_status }}\n\nCompany:\n{{ $json.body.company_name }}\n\nIndustry:\n{{ $json.body.industry }}\n\nWebsite Content:\n{{ $json.website_content }}\n\nReturn JSON with:\n- company_overview\n- products_services\n- recent_initiatives\n- technology_mentioned\n- visible_pain_points"
}
]
}
},
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "5ab34193-b904-4514-9dc0-920b6ccb9cfc",
"name": "Proposal Drafter agent",
"type": "@n8n/n8n-nodes-langchain.anthropic",
"position": [
48,
384
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gemini-2.5-flash",
"cachedResultName": "gemini-2.5-flash"
},
"options": {
"system": "You are a senior proposal strategist at Tiger Analytics, a global leader in AI and analytics consulting trusted by Fortune 1000 companies.\n\nTiger Analytics facts to reference:\n- 4000+ team of technologists and consultants\n- Partnerships with AWS, Google Cloud, Microsoft, Databricks\n- Recognized by Forrester, Gartner, Everest Group, ISG\n- 2025 Databricks Enterprise AI Partner of the Year\n- Microsoft Partner of the Year finalist 2025\n- Accelerators: TigerML, Tiger Blueprints, Tiger DataSphere, Tiger AI Hub\n- Industries: CPG, Retail, BFS, Insurance, Manufacturing, Life Sciences, Healthcare\n\nYour job is to write proposals that sound like they were written by a 10-year industry veteran \u2014 not a chatbot.\n\nRules:\n- Always use the actual company name \u2014 never [Client Name]\n- Always use industry-specific KPIs and terminology from the knowledge base\n- Every number must be specific and grounded in the research provided\n- Tailor every section to the decision maker role\n- Never use markdown formatting inside JSON values\n- Never use ** bold or bullet points with * or -\n- Write plain prose strings only\n- Never include real newlines inside JSON string values\n- Return ONLY valid JSON \u2014 no backticks, no explanation",
"maxTokens": 4096
},
"messages": {
"values": [
{
"content": "==Generate a complete McKinsey-level enterprise consulting proposal for Tiger Analytics.\n\nCLIENT DETAILS:\nCompany: {{ $json.body?.company_name || $json.company_name }}\nIndustry: {{ $json.body?.industry || $json.industry }}\nDecision Maker: {{ $json.body?.decision_maker || $json.decision_maker }}\nProblem Statement: {{ $json.body?.problem_statement || $json.problem_statement }}\nBusiness Impact: {{ $json.body?.business_impact || $json.business_impact }}\nBudget Range: {{ $json.body?.budget_range || $json.budget_range }}\nTimeline: {{ $json.body?.timeline || $json.timeline }}\nDelivery Type: {{ $json.body?.delivery_type || $json.delivery_type }}\nTechnologies: {{ JSON.stringify($json.body?.technologies || $json.technologies || []) }}\n\nCOMPANY RESEARCH:\n{{ JSON.stringify($json.body?.research_data || $json.research_data || {}) }}\n\nINDUSTRY CONTEXT:\n{{ JSON.stringify($json.body?.industry_data || $json.industry_data || {}) }}\n\nSELECTED SOLUTION:\n{{ JSON.stringify($json.body?.selected_solution || $json.selected_solution || {}) }}\n\nPAIN POINT MAPPING:\n{{ JSON.stringify($json.pain_map || {}) }}\n\n{{ $json.body?.kb_context || $json.kb_context || '' }}\n\nCRITICAL INSTRUCTIONS:\n- Use actual company name throughout \u2014 NEVER use [Client Name] or placeholders\n- Use industry-specific terminology from the KB above\n- Reference at least 3 KPIs by name with specific numbers\n- Every number must be specific \u2014 never say \"significant improvement\"\n- Tailor tone completely for the decision maker role\n- Never use markdown, never use ** for bold, never use bullet points\n- Write all values as plain prose strings only\n- Never include newlines inside JSON string values\n- Return ONLY valid JSON \u2014 no backticks, no labels, no explanation\n\nReturn ONLY this exact JSON. Keep each field under 200 words. Be concise:\n{\n\"engagement_title\": \"3-5 word professional engagement name like McKinsey uses. Example: Customer Retention Intelligence Program, Credit Risk Modernization Initiative, Digital Claims Transformation Program. Make it specific to the company and problem.\",\n \"situation_assessment\": \"2-3 sentences max with specific numbers\",\n \"root_cause_diagnosis\": \"3 causes separated by || format: CAUSE 1: title \u2014 desc || CAUSE 2: ...\",\n \"tiger_pov\": \"2-3 sentences max\",\n \"proposed_solution\": \"4 phases separated by || format: PHASE 1 (Wk 1-4): what | Team: who | Outcome: result || PHASE 2...\",\n \"pain_solution_map\": \"3 pains separated by || format: PAIN: x | SEVERITY: y | SOLUTION: z | OUTCOME: metric || ...\",\n \"tiger_accelerators\": \"3 accelerators separated by || format: NAME: x | SAVES: y weeks || ...\",\n \"investment_roi\": \"PHASE 1: $X | PHASE 2: $X | TOTAL: $X || ROI: X% | PAYBACK: X weeks | DAILY COST: $X\",\n \"risk_mitigation\": \"3 risks separated by || format: RISK: x | LIKELIHOOD: y | MITIGATION: z || ...\",\n \"our_team\": \"4 members separated by || format: ROLE: x | EXP: y | SKILL: z || ...\",\n \"credentials\": \"3 credentials separated by || format: AWARD: x | RELEVANCE: y || ...\",\n \"next_steps\": \"4 steps separated by || format: STEP 1 | WHEN: x | ACTION: y || ...\"\n}"
}
]
}
},
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "1d69e131-0253-4917-b82a-ee99a7b545ea",
"name": "Knowledge Base Lookup",
"type": "n8n-nodes-base.code",
"position": [
1776,
-352
],
"parameters": {
"jsCode": "const KB = {\n \"Insurance\": {\n terms: \"lapse rate, persistency ratio, premium, policyholder, underwriting, actuarial, reinsurance, endorsement, claims frequency, loss ratio\",\n kpis: \"loss ratio, combined ratio, lapse rate, persistency rate, claims settlement ratio, NPS, solvency margin\",\n regulations: \"IRDAI guidelines, IFRS17, Solvency II, Insurance Act 1938\",\n pain_points: \"policy lapse, claims fraud, underwriting inaccuracy, mis-selling, slow claims settlement, agent productivity\",\n tech: \"Guidewire, Duck Creek, Majesco, SAS, Salesforce Financial Services Cloud\",\n solution_patterns: \"lapse prediction model, claims fraud detection, underwriting automation, agent performance analytics, customer lifetime value modeling\"\n },\n \"Banking & Financial Services\": {\n terms: \"NPA, CASA ratio, AUM, credit underwriting, KYC, AML, delinquency, write-off, cross-sell, CLTV\",\n kpis: \"Capital Adequacy Ratio, Net Interest Margin, ROE, NPA ratio, CLTV, CASA ratio, cost-to-income ratio\",\n regulations: \"RBI guidelines, Basel III, SEBI regulations, PCI DSS, FEMA, IND AS\",\n pain_points: \"credit risk, AML compliance, customer attrition, cross-sell failure, fraud detection, regulatory reporting\",\n tech: \"Temenos, Finastra, FIS, Murex, Bloomberg Terminal, Salesforce\",\n solution_patterns: \"credit risk scoring, AML transaction monitoring, customer churn prediction, cross-sell propensity model, fraud detection\"\n },\n \"Healthcare\": {\n terms: \"readmission, ALOS, revenue cycle, EMR, prior authorization, claim denial, ICD codes, patient no-show, care gap\",\n kpis: \"bed occupancy rate, ALOS, claim denial rate, readmission rate, patient satisfaction score, net collection rate\",\n regulations: \"HIPAA, HL7, FHIR, NABH, CMS guidelines, HITECH Act\",\n pain_points: \"high readmission rates, revenue cycle inefficiency, staff burnout, patient no-shows, billing errors, care coordination gaps\",\n tech: \"Epic, Cerner, Meditech, Allscripts, Salesforce Health Cloud, AWS HealthLake\",\n solution_patterns: \"readmission prediction, revenue cycle optimization, patient no-show prediction, care gap identification, clinical decision support\"\n },\n \"Retail & E-Commerce\": {\n terms: \"basket size, SKU rationalization, GMV, AOV, CAC, LTV, cart abandonment, planogram, shrinkage, RFM\",\n kpis: \"GMV, AOV, CAC, LTV, cart abandonment rate, inventory turnover, NPS, repeat purchase rate\",\n regulations: \"CCPA, GDPR, PCI DSS, Consumer Protection Act\",\n pain_points: \"cart abandonment, inventory stockouts, high CAC, low repeat purchase, returns fraud, demand forecasting\",\n tech: \"Salesforce Commerce, SAP, Oracle Retail, Shopify, Snowflake, Databricks\",\n solution_patterns: \"churn prediction, demand forecasting, recommendation engine, price optimization, inventory optimization, RFM segmentation\"\n },\n \"Telecom\": {\n terms: \"churn, ARPU, MNP, prepaid/postpaid, network latency, CX transformation, subscriber, MVNO\",\n kpis: \"ARPU, churn rate, NPS, network uptime, customer acquisition cost, CSAT, revenue per user\",\n regulations: \"TRAI guidelines, GDPR, spectrum regulations, DOT compliance\",\n pain_points: \"subscriber churn, ARPU decline, network quality issues, customer service costs, SIM fraud, roaming analytics\",\n tech: \"Amdocs, Oracle Communications, Salesforce, Hadoop, Spark, Kafka\",\n solution_patterns: \"churn prediction, network anomaly detection, ARPU uplift model, customer segmentation, next-best-offer engine\"\n },\n \"Manufacturing\": {\n terms: \"OEE, yield rate, predictive maintenance, downtime, six sigma, OTIF, BOM, MRP, shop floor\",\n kpis: \"OEE, yield rate, downtime percentage, inventory turnover, defect rate, OTIF, scrap rate\",\n regulations: \"ISO 9001, ISO 14001, OSHA, ISO 45001\",\n pain_points: \"unplanned downtime, quality defects, supply chain disruption, high inventory costs, energy waste\",\n tech: \"SAP ERP, Siemens MindSphere, PTC ThingWo
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
anthropicApihttpQueryAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow uses webhooks, Jina AI website scraping, Anthropic-powered agents, and the Google Gemini API to generate consulting proposal options from a company URL, then drafts a structured 11-section proposal and returns it to a frontend as JSON and HTML. Receives a POST…
Source: https://n8n.io/workflows/15894/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
⏺ 🚀 How it works
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.
ANIS_HUB 1. Uses gmail, googleDrive, googleSheets, httpRequest. Webhook trigger; 89 nodes.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.
Remi 1.1. Uses lmChatOpenAi, memoryPostgresChat, openAi, postgres. Webhook trigger; 89 nodes.