{
  "name": "Corey Demo - Intake and CRM verification",
  "nodes": [
    {
      "id": "manual",
      "name": "Manual demo run",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "parameters": {}
    },
    {
      "id": "fixtures",
      "name": "Load synthetic requests",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ],
      "parameters": {
        "jsCode": "return [{json:{\"input_type\": \"synthetic_demo\", \"intake_events\": [{\"event_id\": \"project-101\", \"project_name\": \"Supplier feed cleanup\", \"contact_email\": \"ops@buyer.example\"}, {\"event_id\": \"project-101\", \"project_name\": \"Supplier feed cleanup\", \"contact_email\": \"ops@buyer.example\"}, {\"event_id\": \"project-102\", \"project_name\": \"New catalog\", \"contact_email\": \"ops@buyer.example\", \"simulate_failure\": true}, {\"event_id\": \"project-102\", \"project_name\": \"New catalog\", \"contact_email\": \"ops@buyer.example\"}, {\"event_id\": \"project-103\", \"project_name\": \"Missing contact\"}], \"crm_events\": [{\"event_id\": \"msg-101\", \"email\": \"ops@buyer.example\", \"intent\": \"information\", \"company\": \"Demo Buyer\"}, {\"event_id\": \"msg-102\", \"request_id\": \"quote-101\", \"email\": \"ops@buyer.example\", \"intent\": \"quote\", \"region\": \"UAE\"}, {\"event_id\": \"msg-102\", \"request_id\": \"quote-101\", \"email\": \"ops@buyer.example\", \"intent\": \"quote\", \"region\": \"UAE\"}, {\"event_id\": \"msg-103\", \"request_id\": \"quote-102\", \"email\": \"ops@buyer.example\", \"intent\": \"quote\", \"region\": \"UAE\"}, {\"event_id\": \"msg-104\", \"intent\": \"quote\"}]}}];"
      }
    },
    {
      "id": "rules",
      "name": "Validate deduplicate and recover",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        0
      ],
      "parameters": {
        "jsCode": "// Shared deterministic business rules. No credentials or remote writes.\nconst copy = x => JSON.parse(JSON.stringify(x));\nconst signature = x => JSON.stringify(Object.fromEntries(Object.entries(x).filter(([k])=>k!=='simulate_failure').sort(([a],[b])=>a.localeCompare(b))));\nfunction intake(events, previous={events:{},projects:{},log:[]}) {\n const s=copy(previous); s.events ||= {};s.projects ||= {};s.log ||= [];\n for(const e of events){let status,reason;const key=e.event_id;\n  if(!key || !e.project_name || !e.contact_email){status='review';reason='Missing event ID, project name or contact email';}\n  else if(s.events[key] && s.events[key].signature!==signature(e)){status='review';reason='Event ID reused with a different payload';}\n  else if(s.events[key]?.status==='complete'){status='duplicate';reason='Completed event retained; no second project';}\n  else if(e.simulate_failure){status='retry_pending';reason='Deliberate test destination failure; event retained';s.events[key]={signature:signature(e),status};}\n  else {status='complete';reason=s.events[key]?'Retry completed against the same project key':'Validated request recorded';s.projects[key]={project_id:`project:${key}`,name:e.project_name,contact:e.contact_email};s.events[key]={signature:signature(e),status};}\n  s.log.push({event_id:key??null,status,reason});\n }\n return s;\n}\nfunction routeLeads(events,previous={events:{},contacts:{},deals:{},log:[]}){\n const s=copy(previous);s.events||={};s.contacts||={};s.deals||={};s.log||=[];\n for(const e of events){const email=(e.email||'').trim().toLowerCase(); const event=e.event_id,request=e.request_id;let status,reason,deal_id=null;\n if(!event||!email||!email.includes('@')){status='review';reason='A usable contact identity and event ID are required';}\n else if(s.events[event] && s.events[event]!==signature(e)){status='review';reason='Conflicting payload for the same event';}\n else if(s.events[event]){status='duplicate';reason='Event already handled';}\n else if(e.intent==='quote' && !request){status='review';reason='A request ID is required to create an opportunity';}\n else {s.contacts[email]={email,company:e.company||null};s.events[event]=signature(e);\n if(e.intent==='quote'){deal_id=`${email}:${request}`; const exists=!!s.deals[deal_id];s.deals[deal_id]={contact:email,request_id:request,owner:e.region==='UAE'?'GCC desk':'General desk'};status=exists?'existing_opportunity':'opportunity';reason=exists?'Request already linked to this contact':'Explicit quotation intent and distinct request';}\n else {status='contact_only';reason='Informational enquiry; no opportunity created';}}\n s.log.push({event_id:event??null,contact_key:email||null,request_key:request||null,deal_id,status,reason});\n }return s;\n}\nfunction matchCatalog(rows,products){const seen=new Set();return rows.map(row=>{let status,reason,p=null;const raw=row.manufacturer_sku||row.identifier||row.mpn||''; const key=`${(row.brand||'').toLowerCase()}|${raw.toUpperCase()}|${row.supplier_sku||row.row_id}`;\n if(seen.has(key)){status='duplicate';reason='Same supplier row identity already in this batch';}\n else {seen.add(key);p=products.find(p=>p.brand.toLowerCase()===(row.brand||'').toLowerCase()&&p.identifier.toUpperCase()===raw.toUpperCase());\n if(!p){status='unresolved';reason='No exact reference identifier; title similarity is insufficient';}\n else {const watts=row.total_output_w??row.watts;const known=p.facts.max_total_output_w?.value;\n if(watts!=null&&known!=null&&Number(watts)!==Number(known)){status='conflict';reason=`Feed total ${watts}W differs from manufacturer total ${known}W`;}\n else if(p.identity_grain!=='retail_variant'){status='family_review';reason='Model family found; regional variant is unverified';}\n else if(row.plug_standard && !p.facts.plug_standard){status='configuration_review';reason='Plug standard requested but absent from reference evidence';}\n else {status='matched';reason='Exact brand and manufacturer variant identifier';}}\n }return {...row,status,reason,reference_id:p?.id||null,source_url:p?.source_url||null,facts:p?.facts||{},unknown_fields:p?.unknown_fields||[],write_action:status==='matched'?'propose_upsert':'hold'};\n });}\nfunction monitor(observations,failures=[]){const groups=new Map();for(const o of observations){const k=[o.product_id,o.seller,o.currency,o.market??'unknown'].join('|');if(!groups.has(k))groups.set(k,[]);groups.get(k).push(o);}\n const output=[];for(const [key,list] of groups){list.sort((a,b)=>a.observed_at_utc.localeCompare(b.observed_at_utc));let last;for(const o of list){const change=!last?'baseline':o.price!==last.price?'price_changed':o.availability!==last.availability?'availability_changed':'unchanged';output.push({...o,comparison:key,change,previous_price:last?.price??null,delta:last?Math.round((o.price-last.price)*100)/100:null});last=o;}}\n for(const f of failures)output.push({...f,change:'collection_failed',availability:'unknown'});return output.sort((a,b)=>(a.observed_at_utc||'').localeCompare(b.observed_at_utc||''));}\nconst labels={quote_id:'QUOTE',currency:'Currency',quantity:'Quantity',unit_price:'Unit Price',packaging_total:'Packaging Total',quoted_total:'Quoted Total',lead_time_days:'Lead Time Days',incoterm:'Incoterm'};\nfunction parseQuote(text){const result={};for(const [key,label]of Object.entries(labels)){const match=text.match(new RegExp('^'+label+'(?:\\\\s*:\\\\s*|\\\\s+)([^\\\\n\\\\r]+)','mi'));let value=match?match[1].trim():null;if(value!==null&&['quantity','unit_price','packaging_total','quoted_total','lead_time_days'].includes(key)){value=Number(value);if(!Number.isFinite(value))value=null;}result[key]={value,evidence:match?match[0]:null};}return result;}\nfunction validateQuote(fields,text){const issues=[];const value=k=>fields[k]?.value??null;\n for(const key of Object.keys(labels)){const f=fields[key];if(f?.value==null){issues.push(`${key}: missing`);continue;}const ev=f.evidence||'';const expected=parseQuote(text)[key];if(!ev||!text.includes(ev)||expected.value!==f.value||!ev.toLowerCase().includes(labels[key].toLowerCase()))issues.push(`${key}: source evidence does not support this value`);}\n const nums=['quantity','unit_price','packaging_total','quoted_total'];if(nums.every(k=>typeof value(k)==='number')){const total=Math.round(value('quantity')*Math.round(value('unit_price')*100)+Math.round(value('packaging_total')*100));if(total!==Math.round(value('quoted_total')*100))issues.push('quoted_total: arithmetic mismatch');}\n if(value('currency')!==null&&!/^[A-Z]{3}$/.test(value('currency')))issues.push('currency: ISO code required');return {status:issues.length?'review':'ready_for_approval',issues,calculated_total:nums.every(k=>typeof value(k)==='number')?(Math.round(value('quantity')*Math.round(value('unit_price')*100)+Math.round(value('packaging_total')*100))/100):null,fields};}\nfunction compareQuotes(quotes,{quantity=200,as_of='2026-10-26',currency='USD',incoterm='EXW',pack_count=1}={}){\n return quotes.map(q=>{const issues=[];if(q.currency!==currency)issues.push('Currency differs; no exchange rate assumed');if(q.incoterm!==incoterm)issues.push('Incoterm differs');if(q.moq>quantity)issues.push('MOQ exceeds target quantity');if(q.pack_count!==pack_count)issues.push('Pack configuration differs');if(!q.valid_until||q.valid_until<as_of)issues.push('Quote expired or validity unknown');if(q.unit_price==null||q.packaging_total==null)issues.push('Incomplete quoted cost');const subtotal=q.unit_price!=null&&q.packaging_total!=null?Math.round((quantity*q.unit_price+q.packaging_total)*100)/100:null;return {...q,status:issues.length?'not_comparable':'comparable',issues,target_quantity:quantity,quoted_subtotal:subtotal,shipping:null,duties:null,landed_total:null};}).sort((a,b)=>(a.status==='comparable'?0:1)-(b.status==='comparable'?0:1)||(a.quoted_subtotal??Infinity)-(b.quoted_subtotal??Infinity));}\n\nconst input=$input.first().json; const project=intake(input.intake_events); const crm=routeLeads(input.crm_events);return [{json:{input_type:input.input_type,persistence:'In-execution demo objects only; no durable external record store',project,crm}}];"
      }
    },
    {
      "id": "verify",
      "name": "Verify acceptance cases",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        0
      ],
      "parameters": {
        "jsCode": "const result=$input.first().json;\nconst checks=[{name:'Repeat event creates one project',passed:Object.keys(result.project.projects).length===2},{name:'Intentional destination failure recorded',passed:result.project.log.some(x=>x.status==='retry_pending')},{name:'Retry completes with same identity',passed:result.project.log[3].status==='complete'},{name:'Missing fields sent to review',passed:result.project.log[4].status==='review'},{name:'Informational enquiry creates contact only',passed:result.crm.log[0].status==='contact_only'&&result.crm.log[0].deal_id===null},{name:'Same contact has two distinct requests',passed:Object.keys(result.crm.contacts).length===1&&Object.keys(result.crm.deals).length===2},{name:'Repeated CRM event suppressed',passed:result.crm.log[2].status==='duplicate'},{name:'Missing contact identity held',passed:result.crm.log[4].status==='review'}];\nif(checks.some(c=>!c.passed))throw new Error('Acceptance check failed');return [{json:{status:'verified',checks_passed:checks.length,checks,...result}}];"
      }
    }
  ],
  "connections": {
    "Manual demo run": {
      "main": [
        [
          {
            "node": "Load synthetic requests",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load synthetic requests": {
      "main": [
        [
          {
            "node": "Validate deduplicate and recover",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate deduplicate and recover": {
      "main": [
        [
          {
            "node": "Verify acceptance cases",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}