{
  "name": "Workflow Rescue Demo - Privacy-safe Lead Quality Gate",
  "nodes": [
    {
      "parameters": {"httpMethod": "POST", "path": "workflow-rescue-demo", "responseMode": "responseNode", "options": {}},
      "id": "webhook", "name": "Receive Demo Rows", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [-420, 0]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const inputItems = $input.all();\nconst firstItem = $input.first() ?? inputItems[0];\nconst body = firstItem?.json?.body ?? firstItem?.json ?? {};\nconst rows = Array.isArray(body?.rows) ? body.rows : [body];\nconst clean = value => String(value ?? '').replace(/[\\r\\n]+/g, ' ').replace(/\\s+/g, ' ').trim();\nconst safeExport = value => /^[=+\\-@]/.test(value) ? `'${value}` : value;\nconst field = (row, key) => row && typeof row === 'object' && !Array.isArray(row) ? row[key] : undefined;\nconst emailPattern = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst accepted = [];\nconst duplicates = [];\nconst rejected = [];\nconst seenKeys = new Set();\nconst decision = (row, reason, nextAction, duplicateKey) => {\n  const originalCaseId = safeExport(clean(field(row, 'case_id')));\n  return { case_id: originalCaseId, originalCaseId, email: safeExport(clean(field(row, 'email')).toLowerCase()), source: safeExport(clean(field(row, 'source')) || 'unknown'), request: safeExport(clean(field(row, 'request'))), reason, nextAction, ...(duplicateKey ? { duplicateKey: safeExport(duplicateKey) } : {}) };\n};\nfor (const row of rows) {\n  if (!row || typeof row !== 'object' || Array.isArray(row)) { rejected.push(decision(row, 'row_invalid', 'Provide an object with case_id, email, source, and request')); continue; }\n  const caseId = clean(field(row, 'case_id'));\n  const email = clean(field(row, 'email')).toLowerCase();\n  const request = clean(field(row, 'request'));\n  const duplicateKey = `${email}::${request}`;\n  if (!caseId) { rejected.push(decision(row, 'case_id_required', 'Restore case_id before routing')); continue; }\n  if (!email) { rejected.push(decision(row, 'email_required', 'Request a valid email before routing')); continue; }\n  if (!emailPattern.test(email)) { rejected.push(decision(row, 'invalid_email', 'Correct the email before routing')); continue; }\n  if (!request) { rejected.push(decision(row, 'request_required', 'Request the missing workflow request')); continue; }\n  if (seenKeys.has(duplicateKey)) { duplicates.push(decision(row, 'duplicate_email_and_request_in_batch', 'Merge with the first accepted record in this batch', duplicateKey)); continue; }\n  seenKeys.add(duplicateKey);\n  accepted.push(decision(row, 'accepted', 'Review and route to an owner', duplicateKey));\n}\nconst summary = { input: rows.length, accepted: accepted.length, duplicates: duplicates.length, rejected: rejected.length };\nconst receipt = ['workflow-rescue-demo', 'mode=local-deterministic-fixture', 'duplicate_scope=single_batch', `input=${summary.input}`, `accepted=${summary.accepted}`, `duplicates=${summary.duplicates}`, `rejected=${summary.rejected}`, 'external_send=none'].join('\\n');\nreturn [{ json: { accepted, duplicates, rejected, summary, receipt } }];"
      },
      "id": "quality-gate", "name": "Normalize Validate Deduplicate", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [-100, 0]
    },
    {
      "parameters": {"respondWith": "json", "responseBody": "={{ $json }}", "options": {"responseCode": 200}},
      "id": "respond", "name": "Return Demo Receipt", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [220, 0]
    }
  ],
  "connections": {
    "Receive Demo Rows": {"main": [[{"node": "Normalize Validate Deduplicate", "type": "main", "index": 0}]]},
    "Normalize Validate Deduplicate": {"main": [[{"node": "Return Demo Receipt", "type": "main", "index": 0}]]}
  },
  "pinData": {}, "active": false, "settings": {"executionOrder": "v1"}, "versionId": "00000000-0000-4000-8000-000000000073", "meta": {"templateCredsSetupCompleted": false}, "tags": []
}
