{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "4b7a5274-552f-4b62-bc0b-ec1e58cb9b32",
      "name": "Sticky Note - Intake",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        1376
      ],
      "parameters": {
        "color": 7,
        "width": 420,
        "height": 492,
        "content": "## \ud83d\udce7 PHASE 1: Intelligent Intake & Validation\nMonitors Gmail for resume submissions. Validates PDF attachments and extracts raw text content for AI processing. Handles multiple attachment formats and sizes."
      },
      "typeVersion": 1
    },
    {
      "id": "5018eb75-07bc-48ec-b7eb-8e3936e85cca",
      "name": "Sticky Note - Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        1376
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 540,
        "content": "## \ud83e\udde0 PHASE 2: AI-Powered Resume Analysis\nExtracts structured data: contact info, skills, experience, education. Uses NLP pattern matching and scoring algorithms to calculate qualification metrics. Assigns candidate tier (A/B/C/D)."
      },
      "typeVersion": 1
    },
    {
      "id": "bf6b3e63-6361-4ce7-a672-b0d9923f3937",
      "name": "Sticky Note - Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        416,
        1376
      ],
      "parameters": {
        "color": 7,
        "width": 500,
        "height": 588,
        "content": "## \ud83c\udfaf PHASE 3: Smart Routing & Integration\nQualified candidates (70+ score) \u2192 HubSpot CRM + Slack alert + Drive archive. Unqualified candidates \u2192 Automated rejection email with personalized feedback. All actions logged to analytics."
      },
      "typeVersion": 1
    },
    {
      "id": "7dd69637-b8c1-4419-98d5-91e220ba2efd",
      "name": "Sticky Note - Analytics",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        1376
      ],
      "parameters": {
        "color": 7,
        "width": 740,
        "height": 572,
        "content": "## \ud83d\udcca PHASE 4: Analytics & Feedback Loop\nTracks hiring funnel metrics, candidate quality trends, and time-to-hire. Feeds data back to scoring model for continuous improvement."
      },
      "typeVersion": 1
    },
    {
      "id": "d53b1abf-f411-4d4d-a20f-ca94b27f723a",
      "name": "IF: Valid PDF Attachment?",
      "type": "n8n-nodes-base.if",
      "position": [
        -256,
        1568
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "caseSensitive": false
          },
          "conditions": [
            {
              "id": "attachment-check",
              "operator": {
                "type": "array",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.attachments }}",
              "rightValue": ""
            },
            {
              "id": "pdf-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.attachments[0].mimeType }}",
              "rightValue": "application/pdf"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "4e4e9256-0a2b-48e3-8afe-1c6de2787533",
      "name": "Code: Pre-Validation",
      "type": "n8n-nodes-base.code",
      "position": [
        -384,
        1712
      ],
      "parameters": {
        "jsCode": "// Pre-Processing: Attachment Validation & Metadata Extraction\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const attachments = item.json.attachments || [];\n  \n  if (attachments.length === 0) {\n    results.push({\n      json: {\n        ...item.json,\n        validationError: 'No attachments found',\n        skipProcessing: true\n      }\n    });\n    continue;\n  }\n  \n  // Find PDF attachment\n  const pdfAttachment = attachments.find(att => \n    att.mimeType === 'application/pdf' || \n    att.fileName?.toLowerCase().endsWith('.pdf')\n  );\n  \n  if (!pdfAttachment) {\n    results.push({\n      json: {\n        ...item.json,\n        validationError: 'No PDF resume found',\n        skipProcessing: true\n      }\n    });\n    continue;\n  }\n  \n  // Extract metadata\n  const fileSizeKB = (pdfAttachment.size || 0) / 1024;\n  const applicantEmail = item.json.from?.address || 'user@example.com';\n  const applicantName = item.json.from?.name || 'Unknown Applicant';\n  const receivedDate = item.json.date || new Date().toISOString();\n  const emailSubject = item.json.subject || 'No Subject';\n  \n  // Validate file size (reject if > 10MB)\n  if (fileSizeKB > 10240) {\n    results.push({\n      json: {\n        ...item.json,\n        validationError: 'File size exceeds 10MB limit',\n        skipProcessing: true,\n        fileSizeKB: fileSizeKB.toFixed(2)\n      }\n    });\n    continue;\n  }\n  \n  results.push({\n    json: {\n      ...item.json,\n      pdfAttachment: pdfAttachment,\n      applicantEmail: applicantEmail,\n      applicantName: applicantName,\n      receivedDate: receivedDate,\n      emailSubject: emailSubject,\n      fileSizeKB: fileSizeKB.toFixed(2),\n      skipProcessing: false,\n      processingTimestamp: new Date().toISOString()\n    },\n    binary: item.binary\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "f9c391fd-a9b9-4213-940e-94b439bb6106",
      "name": "PDF to Text: Extract Content",
      "type": "n8n-nodes-htmlcsstopdf.htmlcsstopdf",
      "position": [
        -32,
        1568
      ],
      "parameters": {
        "resource": "pdfManipulation",
        "operation": "parsePdfToJson"
      },
      "credentials": {
        "htmlcsstopdfApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8b37d04a-0ebf-46fd-8819-8ee9bb01401f",
      "name": "Code: AI Resume Parser",
      "type": "n8n-nodes-base.code",
      "position": [
        176,
        1568
      ],
      "parameters": {
        "jsCode": "// Advanced Resume Parser with Enhanced Extraction\nconst item = $input.first();\nconst text = item.json.text || '';\nconst fullText = text.toLowerCase();\n\n// ==================== CONTACT INFORMATION ====================\n// Extract Email (prioritize from PDF content)\nconst emailMatches = text.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})/gi) || [];\nconst email = emailMatches[0] || item.json.applicantEmail || 'user@example.com';\n\n// Extract Phone (multiple formats)\nconst phonePatterns = [\n  /\\+?1?[-.]?\\(?([0-9]{3})\\)?[-.]?([0-9]{3})[-.]?([0-9]{4})/,\n  /\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})/,\n  /([0-9]{3})[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})/\n];\nlet phone = 'Not Found';\nfor (const pattern of phonePatterns) {\n  const match = text.match(pattern);\n  if (match) {\n    phone = match[0];\n    break;\n  }\n}\n\n// Extract LinkedIn\nconst linkedInMatch = text.match(/linkedin\\.com\\/in\\/([a-zA-Z0-9-]+)/i);\nconst linkedIn = linkedInMatch ? linkedInMatch[0] : 'Not Found';\n\n// Extract Location (City, State)\nconst locationMatch = text.match(/([A-Z][a-z]+(?:\\s[A-Z][a-z]+)*),\\s*([A-Z]{2})/i);\nconst location = locationMatch ? `${locationMatch[1]}, ${locationMatch[2]}` : 'Not Found';\n\n// ==================== SKILLS EXTRACTION ====================\nconst skillCategories = {\n  programming: ['Python', 'JavaScript', 'Java', 'C\\\\+\\\\+', 'C#', 'Ruby', 'PHP', 'Swift', 'Kotlin', 'Go', 'Rust', 'TypeScript'],\n  frontend: ['React', 'Angular', 'Vue', 'HTML', 'CSS', 'jQuery', 'Bootstrap', 'Tailwind'],\n  backend: ['Node.js', 'Express', 'Django', 'Flask', 'Spring', 'Laravel', '.NET', 'FastAPI'],\n  database: ['SQL', 'MySQL', 'PostgreSQL', 'MongoDB', 'Redis', 'Oracle', 'DynamoDB', 'Cassandra'],\n  cloud: ['AWS', 'Azure', 'GCP', 'Heroku', 'DigitalOcean', 'Kubernetes', 'Docker'],\n  tools: ['Git', 'Jenkins', 'JIRA', 'Postman', 'Selenium', 'Terraform', 'Ansible'],\n  data: ['Machine Learning', 'Data Science', 'Pandas', 'NumPy', 'TensorFlow', 'PyTorch', 'Tableau', 'Power BI']\n};\n\nconst foundSkills = {\n  programming: [],\n  frontend: [],\n  backend: [],\n  database: [],\n  cloud: [],\n  tools: [],\n  data: [],\n  all: []\n};\n\nfor (const [category, skills] of Object.entries(skillCategories)) {\n  for (const skill of skills) {\n    const regex = new RegExp(`\\\\b${skill}\\\\b`, 'i');\n    if (regex.test(text)) {\n      foundSkills[category].push(skill);\n      foundSkills.all.push(skill);\n    }\n  }\n}\n\n// ==================== EXPERIENCE EXTRACTION ====================\n// Extract year ranges (e.g., 2018-2022, 2020-Present)\nconst yearRanges = text.match(/\\b(20\\d{2}|19\\d{2})\\s*[-\u2013\u2014]\\s*(?:(20\\d{2}|19\\d{2})|present|current)/gi) || [];\nlet totalYears = 0;\n\nfor (const range of yearRanges) {\n  const match = range.match(/\\b(20\\d{2}|19\\d{2})\\s*[-\u2013\u2014]\\s*(?:(20\\d{2}|19\\d{2})|present|current)/i);\n  if (match) {\n    const startYear = parseInt(match[1]);\n    const endYear = match[2] ? parseInt(match[2]) : new Date().getFullYear();\n    totalYears += (endYear - startYear);\n  }\n}\n\n// Fallback: Look for explicit years of experience\nconst expMatch = text.match(/(\\d+)\\+?\\s*years?\\s*(?:of)?\\s*experience/i);\nif (expMatch && totalYears === 0) {\n  totalYears = parseInt(expMatch[1]);\n}\n\n// ==================== EDUCATION EXTRACTION ====================\nconst degreeKeywords = {\n  phd: /ph\\.?d|doctorate|doctoral/i,\n  masters: /master'?s?|m\\.?s\\.?|m\\.?a\\.?|mba/i,\n  bachelors: /bachelor'?s?|b\\.?s\\.?|b\\.?a\\.?|b\\.?tech|b\\.?e\\.?/i,\n  associate: /associate'?s?|a\\.?s\\.?|a\\.?a\\.?/i\n};\n\nlet highestDegree = 'None';\nlet hasDegree = false;\n\nif (degreeKeywords.phd.test(text)) {\n  highestDegree = 'PhD';\n  hasDegree = true;\n} else if (degreeKeywords.masters.test(text)) {\n  highestDegree = 'Masters';\n  hasDegree = true;\n} else if (degreeKeywords.bachelors.test(text)) {\n  highestDegree = 'Bachelors';\n  hasDegree = true;\n} else if (degreeKeywords.associate.test(text)) {\n  highestDegree = 'Associate';\n  hasDegree = true;\n}\n\n// Extract University/College names\nconst universityMatch = text.match(/(?:university|college|institute)\\s+of\\s+([A-Z][a-z]+(?:\\s[A-Z][a-z]+)*)/i);\nconst university = universityMatch ? universityMatch[0] : 'Not Found';\n\n// ==================== CERTIFICATIONS ====================\nconst certKeywords = ['AWS Certified', 'Google Certified', 'Microsoft Certified', 'PMP', 'CISSP', 'Scrum Master', 'Six Sigma'];\nconst certifications = [];\nfor (const cert of certKeywords) {\n  if (new RegExp(`\\\\b${cert}\\\\b`, 'i').test(text)) {\n    certifications.push(cert);\n  }\n}\n\n// ==================== SCORING ALGORITHM ====================\nlet score = 0;\n\n// Skills Score (max 40 points)\nscore += Math.min(foundSkills.all.length * 4, 40);\n\n// Experience Score (max 30 points)\nif (totalYears >= 10) score += 30;\nelse if (totalYears >= 7) score += 25;\nelse if (totalYears >= 5) score += 20;\nelse if (totalYears >= 3) score += 15;\nelse if (totalYears >= 1) score += 10;\nelse score += 5;\n\n// Education Score (max 20 points)\nif (highestDegree === 'PhD') score += 20;\nelse if (highestDegree === 'Masters') score += 15;\nelse if (highestDegree === 'Bachelors') score += 10;\nelse if (highestDegree === 'Associate') score += 5;\n\n// Certification Bonus (max 10 points)\nscore += Math.min(certifications.length * 5, 10);\n\n// Ensure score doesn't exceed 100\nscore = Math.min(score, 100);\n\n// ==================== QUALIFICATION TIERS ====================\nconst qualified = score >= 70;\nconst tier = score >= 90 ? 'A+' : \n             score >= 85 ? 'A' : \n             score >= 70 ? 'B' : \n             score >= 50 ? 'C' : 'D';\n\nconst tierDescription = {\n  'A+': 'Exceptional Candidate - Immediate Interview',\n  'A': 'Strong Candidate - Priority Review',\n  'B': 'Qualified Candidate - Standard Review',\n  'C': 'Marginal Candidate - Consider for Junior Roles',\n  'D': 'Unqualified - Send Rejection'\n};\n\n// ==================== KEYWORD MATCHING ====================\nconst jobKeywords = ['agile', 'scrum', 'ci/cd', 'rest api', 'microservices', 'devops', 'leadership', 'team lead'];\nconst matchedKeywords = [];\nfor (const keyword of jobKeywords) {\n  if (new RegExp(`\\\\b${keyword}\\\\b`, 'i').test(text)) {\n    matchedKeywords.push(keyword);\n  }\n}\n\n// ==================== RETURN ENRICHED DATA ====================\nitem.json.candidateEmail = email;\nitem.json.candidatePhone = phone;\nitem.json.candidateLinkedIn = linkedIn;\nitem.json.candidateLocation = location;\nitem.json.skills = foundSkills.all;\nitem.json.skillsByCategory = {\n  programming: foundSkills.programming,\n  frontend: foundSkills.frontend,\n  backend: foundSkills.backend,\n  database: foundSkills.database,\n  cloud: foundSkills.cloud,\n  tools: foundSkills.tools,\n  data: foundSkills.data\n};\nitem.json.totalSkills = foundSkills.all.length;\nitem.json.yearsExperience = totalYears;\nitem.json.highestDegree = highestDegree;\nitem.json.hasDegree = hasDegree;\nitem.json.university = university;\nitem.json.certifications = certifications;\nitem.json.matchedKeywords = matchedKeywords;\nitem.json.qualificationScore = score;\nitem.json.qualified = qualified;\nitem.json.tier = tier;\nitem.json.tierDescription = tierDescription[tier];\nitem.json.candidateName = item.json.applicantName || 'Unknown';\nitem.json.analysisTimestamp = new Date().toISOString();\n\nreturn item;"
      },
      "typeVersion": 2
    },
    {
      "id": "610619d2-f3d6-4463-9cdc-bbd7e189f3c5",
      "name": "PostgreSQL: Log Application",
      "type": "n8n-nodes-base.postgres",
      "position": [
        192,
        1760
      ],
      "parameters": {
        "query": "=INSERT INTO candidate_applications (\n  email,\n  name,\n  phone,\n  linkedin,\n  location,\n  skills,\n  years_experience,\n  highest_degree,\n  university,\n  certifications,\n  qualification_score,\n  tier,\n  qualified,\n  received_date,\n  processed_date\n) VALUES (\n  '{{ $json.candidateEmail }}',\n  '{{ $json.candidateName }}',\n  '{{ $json.candidatePhone }}',\n  '{{ $json.candidateLinkedIn }}',\n  '{{ $json.candidateLocation }}',\n  '{{ JSON.stringify($json.skills) }}',\n  {{ $json.yearsExperience }},\n  '{{ $json.highestDegree }}',\n  '{{ $json.university }}',\n  '{{ JSON.stringify($json.certifications) }}',\n  {{ $json.qualificationScore }},\n  '{{ $json.tier }}',\n  {{ $json.qualified }},\n  '{{ $json.receivedDate }}',\n  NOW()\n) RETURNING id;",
        "options": {},
        "operation": "executeQuery"
      },
      "typeVersion": 2.4
    },
    {
      "id": "85f186c5-06fa-4f3a-af49-53a1b61f9db1",
      "name": "IF: Qualified Candidate?",
      "type": "n8n-nodes-base.if",
      "position": [
        416,
        1568
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {},
          "conditions": [
            {
              "id": "qualified-check",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.qualified }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "dd2ca3bb-9855-48ec-9ad2-e4eb766f05e0",
      "name": "HubSpot: Create Contact",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        624,
        1456
      ],
      "parameters": {
        "operation": "create",
        "authentication": "appToken"
      },
      "credentials": {
        "hubspotAppToken": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "4befb2f3-3469-4f63-905c-447720df8d01",
      "name": "Slack: Qualified Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        848,
        1456
      ],
      "parameters": {
        "text": "=\ud83c\udfaf *New Qualified Candidate!*\n\n*Name:* {{ $json.candidateName }}\n*Email:* {{ $json.candidateEmail }}\n*Phone:* {{ $json.candidatePhone }}\n*Location:* {{ $json.candidateLocation }}\n\n*\ud83d\udcca Qualification Details:*\n\u2022 *Score:* {{ $json.qualificationScore }}/100\n\u2022 *Tier:* {{ $json.tier }} - {{ $json.tierDescription }}\n\u2022 *Experience:* {{ $json.yearsExperience }} years\n\u2022 *Education:* {{ $json.highestDegree }} from {{ $json.university }}\n\n*\ud83d\udcbc Skills ({{ $json.totalSkills }}):*\n{{ $json.skills.slice(0, 10).join(', ') }}{{ $json.totalSkills > 10 ? '...' : '' }}\n\n*\ud83c\udf93 Certifications:*\n{{ $json.certifications.length > 0 ? $json.certifications.join(', ') : 'None listed' }}\n\n*\ud83d\udd17 LinkedIn:* {{ $json.candidateLinkedIn }}\n\n*\u2705 Action:* Contact added to HubSpot. Resume archived to Google Drive.\n*\ud83d\udcc5 Received:* {{ $now.toFormat('MMM dd, yyyy HH:mm') }}",
        "select": "channel",
        "channelId": "={{ $vars.SLACK_HR_CHANNEL_ID }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 2.1
    },
    {
      "id": "0ca06d09-1ce1-4b1a-b641-d106444f4f8e",
      "name": "Google Drive: Archive Qualified",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        624,
        1568
      ],
      "parameters": {
        "name": "={{ $json.candidateName.replace(/\\s+/g, '_') }}_Resume_{{ $now.toFormat('yyyy-MM-dd') }}.pdf",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "qualified_resumes_folder_id"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "f2aed3d3-efe7-442d-b0c9-d688b4c43c0f",
      "name": "Google Drive: Archive Rejected",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        624,
        1808
      ],
      "parameters": {
        "name": "={{ $json.candidateName.replace(/\\s+/g, '_') }}_Resume_{{ $now.toFormat('yyyy-MM-dd') }}.pdf",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "rejected_resumes_folder_id"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "4e4ff844-1f01-4082-9c88-4a2d5ff85806",
      "name": "Gmail: Send Rejection",
      "type": "n8n-nodes-base.gmail",
      "position": [
        848,
        1664
      ],
      "parameters": {
        "sendTo": "={{ $json.candidateEmail }}",
        "message": "=<!DOCTYPE html>\n<html>\n<head>\n  <style>\n    body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }\n    .container { max-width: 600px; margin: 0 auto; padding: 20px; }\n    .header { background: #f4f4f4; padding: 20px; text-align: center; border-bottom: 3px solid #e74c3c; }\n    .content { padding: 20px; }\n    .footer { background: #f4f4f4; padding: 15px; text-align: center; font-size: 12px; color: #888; }\n    .score-box { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <h2>Thank You for Your Application</h2>\n    </div>\n    <div class=\"content\">\n      <p>Dear {{ $json.candidateName.split(' ')[0] }},</p>\n      \n      <p>Thank you for your interest in joining our team. We have carefully reviewed your application and resume.</p>\n      \n      <div class=\"score-box\">\n        <strong>Application Assessment:</strong><br/>\n        Qualification Score: {{ $json.qualificationScore }}/100 (Tier {{ $json.tier }})<br/>\n        Experience: {{ $json.yearsExperience }} years<br/>\n        Education: {{ $json.highestDegree }}\n      </div>\n      \n      <p>After careful consideration, we have decided to move forward with other candidates whose qualifications more closely match our current requirements at this time.</p>\n      \n      <p><strong>Feedback for your professional development:</strong></p>\n      <ul>\n        <li>Your application showed {{ $json.totalSkills }} relevant skills</li>\n        <li>Consider gaining more experience in: {{ $json.skillsByCategory.cloud.length === 0 ? 'Cloud technologies (AWS, Azure)' : 'Advanced frameworks' }}</li>\n        <li>{{ $json.certifications.length === 0 ? 'Professional certifications could strengthen your profile' : 'Your certifications are a strong asset' }}</li>\n      </ul>\n      \n      <p>We encourage you to apply for future openings that may better align with your qualifications. We will keep your resume on file for 6 months.</p>\n      \n      <p>We wish you the best of luck in your job search and future career endeavors.</p>\n      \n      <p>Best regards,<br/>\n      <strong>HR Team</strong></p>\n    </div>\n    <div class=\"footer\">\n      <p>This is an automated message. Please do not reply directly to this email.</p>\n      <p>Follow us on LinkedIn for future opportunities!</p>\n    </div>\n  </div>\n</body>\n</html>",
        "options": {
          "ccList": "",
          "bccList": ""
        },
        "subject": "=Thank you for your application - {{ $json.candidateName }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "9d50522c-317c-4212-9878-be42c3b51332",
      "name": "Slack: Rejection Log",
      "type": "n8n-nodes-base.slack",
      "position": [
        848,
        1808
      ],
      "parameters": {
        "text": "=\u274c *Application Rejected*\n\n*Name:* {{ $json.candidateName }}\n*Email:* {{ $json.candidateEmail }}\n*Score:* {{ $json.qualificationScore }}/100 (Tier {{ $json.tier }})\n*Experience:* {{ $json.yearsExperience }} years\n*Skills:* {{ $json.totalSkills }} identified\n\n*Reason:* Score below 70 threshold\n*Action:* Rejection email sent. Resume archived.",
        "select": "channel",
        "channelId": "={{ $vars.SLACK_HR_CHANNEL_ID }}",
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.1
    },
    {
      "id": "5516c15a-43fb-4387-8271-f5fd64bd1cca",
      "name": "Merge: Qualified Path",
      "type": "n8n-nodes-base.merge",
      "position": [
        1072,
        1552
      ],
      "parameters": {},
      "typeVersion": 2.1
    },
    {
      "id": "3fbfa060-63c3-43ef-8668-59a0204b7b2d",
      "name": "Merge: Rejected Path",
      "type": "n8n-nodes-base.merge",
      "position": [
        1088,
        1776
      ],
      "parameters": {},
      "typeVersion": 2.1
    },
    {
      "id": "fe7f00a4-4470-4b17-ab65-a681980f36de",
      "name": "Code: Analytics Calculator",
      "type": "n8n-nodes-base.code",
      "position": [
        1296,
        1632
      ],
      "parameters": {
        "jsCode": "// Analytics Aggregator: Calculate Hiring Funnel Metrics\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const data = item.json;\n  \n  // Calculate processing time\n  const received = new Date(data.receivedDate);\n  const processed = new Date(data.analysisTimestamp);\n  const processingTimeSeconds = (processed - received) / 1000;\n  \n  // Determine outcome\n  const outcome = data.qualified ? 'Accepted' : 'Rejected';\n  \n  // Calculate skill match percentage\n  const totalPossibleSkills = 45; // Sum of all skill categories\n  const skillMatchPercentage = ((data.totalSkills / totalPossibleSkills) * 100).toFixed(1);\n  \n  results.push({\n    json: {\n      ...data,\n      analytics: {\n        outcome: outcome,\n        processingTimeSeconds: processingTimeSeconds.toFixed(2),\n        skillMatchPercentage: skillMatchPercentage,\n        hasLinkedIn: data.candidateLinkedIn !== 'Not Found',\n        hasCertifications: data.certifications.length > 0,\n        degreeLevel: data.highestDegree,\n        experienceLevel: data.yearsExperience >= 7 ? 'Senior' : \n                        data.yearsExperience >= 3 ? 'Mid-Level' : 'Junior',\n        timestamp: new Date().toISOString()\n      }\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "57e30780-87ae-487b-a7af-eb04966b429f",
      "name": "PostgreSQL: Store Analytics",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1520,
        1632
      ],
      "parameters": {
        "query": "=INSERT INTO hiring_funnel_analytics (\n  outcome,\n  qualification_score,\n  tier,\n  years_experience,\n  experience_level,\n  total_skills,\n  skill_match_percentage,\n  highest_degree,\n  has_certifications,\n  has_linkedin,\n  processing_time_seconds,\n  received_date,\n  processed_date\n) VALUES (\n  '{{ $json.analytics.outcome }}',\n  {{ $json.qualificationScore }},\n  '{{ $json.tier }}',\n  {{ $json.yearsExperience }},\n  '{{ $json.analytics.experienceLevel }}',\n  {{ $json.totalSkills }},\n  {{ $json.analytics.skillMatchPercentage }},\n  '{{ $json.highestDegree }}',\n  {{ $json.analytics.hasCertifications }},\n  {{ $json.analytics.hasLinkedIn }},\n  {{ $json.analytics.processingTimeSeconds }},\n  '{{ $json.receivedDate }}',\n  '{{ $json.analysisTimestamp }}'\n);",
        "options": {},
        "operation": "executeQuery"
      },
      "typeVersion": 2.4
    },
    {
      "id": "7594e701-4305-4be0-ad52-0cbd223932fe",
      "name": "Documentation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1296,
        672
      ],
      "parameters": {
        "width": 692,
        "height": 656,
        "content": "## \u2696\ufe0f Talent Sovereign: AI Resume Intelligence Hub\n\nIndustrial-grade recruitment pipeline: Gmail Intake \u2192 AI Parsing \u2192 Scoring \u2192 Smart Routing.\n\n### \u2699\ufe0f Core Sovereign Logic\n* **PHASE 1: Intake & Validation:** Monitors Gmail for PDF resumes; validates file integrity and metadata.\n* **PHASE 2: AI Parsing:** Uses **Parse PDF to JSON** to extract skills, experience, and contact data.\n* **PHASE 3: Tiered Scoring:** Custom algorithm calculates a 100-pt score and assigns Tiers (A+ to D).\n* **PHASE 4: Smart Routing:** - **Qualified (70+):** Syncs to HubSpot CRM, archives to 'Qualified' Drive, and alerts Slack.\n    - **Rejected (<70):** Sends personalized feedback email and archives to 'Rejected' Drive.\n* **PHASE 5: Analytics:** Logs funnel metrics (time-to-hire, skill trends) to PostgreSQL.\n\n### \ud83d\udccb Setup\n1. **Drive:** Create 'Qualified' and 'Rejected' folders.\n2. **CRM:** Connect HubSpot App Token.\n3. **DB:** Prepare `candidate_applications` table in Postgres.\n\n**Metrics:** `Qualification_Score`, `Skill_Match_%`, `Funnel_Conversion`."
      },
      "typeVersion": 1
    },
    {
      "id": "73ab0fd9-de75-4100-84bb-dfdb49025579",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        -528,
        1568
      ],
      "parameters": {
        "filters": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    }
  ],
  "connections": {
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "Code: Pre-Validation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Pre-Validation": {
      "main": [
        [
          {
            "node": "IF: Valid PDF Attachment?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack: Rejection Log": {
      "main": [
        [
          {
            "node": "Merge: Rejected Path",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Gmail: Send Rejection": {
      "main": [
        [
          {
            "node": "Slack: Rejection Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge: Qualified Path": {
      "main": [
        [
          {
            "node": "Code: Analytics Calculator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: AI Resume Parser": {
      "main": [
        [
          {
            "node": "PostgreSQL: Log Application",
            "type": "main",
            "index": 0
          },
          {
            "node": "IF: Qualified Candidate?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack: Qualified Alert": {
      "main": [
        [
          {
            "node": "Merge: Qualified Path",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: Create Contact": {
      "main": [
        [
          {
            "node": "Slack: Qualified Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Qualified Candidate?": {
      "main": [
        [
          {
            "node": "HubSpot: Create Contact",
            "type": "main",
            "index": 0
          },
          {
            "node": "Google Drive: Archive Qualified",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Drive: Archive Rejected",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail: Send Rejection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Valid PDF Attachment?": {
      "main": [
        [
          {
            "node": "PDF to Text: Extract Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Analytics Calculator": {
      "main": [
        [
          {
            "node": "PostgreSQL: Store Analytics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDF to Text: Extract Content": {
      "main": [
        [
          {
            "node": "Code: AI Resume Parser",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive: Archive Rejected": {
      "main": [
        [
          {
            "node": "Merge: Rejected Path",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive: Archive Qualified": {
      "main": [
        [
          {
            "node": "Merge: Qualified Path",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}