{
  "blueprint_version": "1.0",
  "product": "DevFilter Starter Screening Kit",
  "workflow_name": "Developer Application Auto-Screen",
  "_comment": "This is a realistic Zapier blueprint-style JSON file meant to be adapted inside Zapier. It uses standard Zapier building blocks and includes inline _comment fields because native JSON comments are not allowed.",
  "goal": "Capture new job applications, normalize the data, check GitHub activity, score the applicant against the target stack, and send an automatic pass/fail reply.",
  "recommended_apps": {
    "form_source": [
      "Typeform",
      "Google Forms",
      "Tally",
      "Jotform"
    ],
    "email_provider": [
      "Gmail",
      "Outlook",
      "Mailgun"
    ]
  },
  "assumptions": {
    "target_role": "Full-Stack React / Node.js Engineer",
    "pass_threshold": 70,
    "github_recency_days": 180,
    "required_fields": [
      "full_name",
      "email",
      "linkedin_url",
      "github_url",
      "years_experience",
      "location",
      "work_authorization",
      "resume_url",
      "portfolio_url",
      "stack_summary",
      "notable_projects"
    ]
  },
  "trigger": {
    "id": "trigger_new_application",
    "app": "Typeform",
    "event": "New Entry",
    "_comment": "Swap this trigger for Google Forms or another form source if needed. The important part is receiving a structured application payload whenever someone applies.",
    "config": {
      "form_name": "Engineering Application Form",
      "polling_frequency": "instant",
      "sample_fields": {
        "submitted_at": "2026-05-19T14:12:44Z",
        "full_name": "Jordan Lee",
        "email": "jordan@example.com",
        "linkedin_url": "https://www.linkedin.com/in/jordanlee",
        "github_url": "https://github.com/jordanlee",
        "years_experience": "4",
        "location": "Austin, TX",
        "work_authorization": "US Citizen",
        "resume_url": "https://files.example.com/resume-jordan-lee.pdf",
        "portfolio_url": "https://jordanlee.dev",
        "stack_summary": "React, Next.js, Node.js, TypeScript, PostgreSQL, GraphQL",
        "notable_projects": "Built an internal design system, led migration from REST to GraphQL, and improved Lighthouse performance from 58 to 91."
      }
    }
  },
  "steps": [
    {
      "id": "step_parse_applicant_data",
      "app": "Formatter by Zapier",
      "event": "Utilities",
      "_comment": "Normalize the raw form payload into clean fields the rest of the workflow can trust. This is where you trim URLs, standardize casing, and derive helper values.",
      "actions": [
        {
          "name": "extract_github_username",
          "type": "text_replace",
          "input": "{{trigger.github_url}}",
          "rule": "Remove https://github.com/ and trailing slash",
          "output_key": "github_username"
        },
        {
          "name": "normalize_stack_text",
          "type": "text_lowercase",
          "input": "{{trigger.stack_summary}} {{trigger.notable_projects}}",
          "output_key": "normalized_stack_text"
        },
        {
          "name": "coerce_years_experience",
          "type": "number_parse",
          "input": "{{trigger.years_experience}}",
          "output_key": "years_experience_number"
        }
      ],
      "outputs": {
        "github_username": "jordanlee",
        "normalized_stack_text": "react next.js node.js typescript postgresql graphql built an internal design system led migration from rest to graphql and improved lighthouse performance from 58 to 91",
        "years_experience_number": 4
      }
    },
    {
      "id": "step_check_github_profile",
      "app": "Webhooks by Zapier",
      "event": "Custom Request",
      "_comment": "Use GitHub's public API to pull lightweight profile data. No login is required for low-volume use, but teams can add a token header later if they expect more traffic.",
      "request": {
        "method": "GET",
        "url": "https://api.github.com/users/{{step_parse_applicant_data.outputs.github_username}}",
        "headers": {
          "Accept": "application/vnd.github+json",
          "User-Agent": "devfilter-screening-zap"
        }
      },
      "selected_fields": [
        "public_repos",
        "followers",
        "following",
        "created_at",
        "updated_at",
        "bio",
        "blog"
      ],
      "outputs": {
        "public_repos": 18,
        "followers": 42,
        "updated_at": "2026-05-14T19:21:09Z",
        "bio": "Full-stack engineer focused on React, Node.js, DX, and internal tooling.",
        "blog": "https://jordanlee.dev"
      }
    },
    {
      "id": "step_score_applicant",
      "app": "Code by Zapier",
      "event": "Run JavaScript",
      "_comment": "The scoring step combines keyword matching from the application with a few basic GitHub activity signals. Keep this lightweight so it remains transparent and easy to tune by recruiters.",
      "input_data": {
        "normalized_text": "{{step_parse_applicant_data.outputs.normalized_stack_text}} {{step_check_github_profile.outputs.bio}}",
        "years_experience": "{{step_parse_applicant_data.outputs.years_experience_number}}",
        "public_repos": "{{step_check_github_profile.outputs.public_repos}}",
        "github_updated_at": "{{step_check_github_profile.outputs.updated_at}}"
      },
      "script": "const text = (inputData.normalized_text || '').toLowerCase();\nconst years = Number(inputData.years_experience || 0);\nconst repos = Number(inputData.public_repos || 0);\nconst updatedAt = new Date(inputData.github_updated_at || 0);\nconst requiredKeywords = ['react', 'node', 'typescript'];\nconst bonusKeywords = ['next.js', 'postgresql', 'graphql', 'testing', 'aws', 'docker'];\nconst mismatchKeywords = ['wordpress', 'shopify', 'bubble', 'wix'];\nlet score = 0;\nconst matchedRequired = requiredKeywords.filter((keyword) => text.includes(keyword));\nconst matchedBonus = bonusKeywords.filter((keyword) => text.includes(keyword));\nconst matchedMismatch = mismatchKeywords.filter((keyword) => text.includes(keyword));\nscore += matchedRequired.length * 20;\nscore += matchedBonus.length * 5;\nif (years >= 3) score += 10;\nif (repos >= 5) score += 10;\nconst daysSinceUpdate = Math.floor((Date.now() - updatedAt.getTime()) / 86400000);\nif (daysSinceUpdate >= 0 && daysSinceUpdate <= 180) score += 10;\nscore -= matchedMismatch.length * 10;\nif (score < 0) score = 0;\nif (score > 100) score = 100;\nconst decision = score >= 70 ? 'pass' : 'fail';\nconst rationale = [];\nif (matchedRequired.length) rationale.push(`Required stack match: ${matchedRequired.join(', ')}`);\nif (matchedBonus.length) rationale.push(`Bonus signals: ${matchedBonus.join(', ')}`);\nif (years >= 3) rationale.push('Experience threshold met');\nif (repos >= 5) rationale.push('Active public GitHub footprint');\nif (daysSinceUpdate >= 0 && daysSinceUpdate <= 180) rationale.push('Recent GitHub activity');\nif (matchedMismatch.length) rationale.push(`Potential mismatch terms: ${matchedMismatch.join(', ')}`);\nreturn {\n  score,\n  decision,\n  matched_required: matchedRequired.join(', '),\n  matched_bonus: matchedBonus.join(', '),\n  mismatch_terms: matchedMismatch.join(', '),\n  rationale: rationale.join(' | '),\n  recruiter_summary: `Score ${score}/100. Decision: ${decision}. ${rationale.join(' | ')}`\n};"
    },
    {
      "id": "step_route_by_score",
      "app": "Paths by Zapier",
      "event": "Create Paths",
      "_comment": "Split the workflow into a pass path and a fail path so the email copy stays easy to manage and recruiters can later add separate Slack or ATS actions to each branch.",
      "paths": [
        {
          "name": "pass_path",
          "rule": "{{step_score_applicant.outputs.decision}} exactly matches pass"
        },
        {
          "name": "fail_path",
          "rule": "{{step_score_applicant.outputs.decision}} exactly matches fail"
        }
      ]
    },
    {
      "id": "step_send_pass_email",
      "path": "pass_path",
      "app": "Gmail",
      "event": "Send Email",
      "_comment": "Send a next-step message to candidates who cleared the first filter. Reference the technical screening invite template from the starter kit and personalize it with the matched stack signals.",
      "config": {
        "to": "{{trigger.email}}",
        "subject": "Next step: technical screening for {{assumptions.target_role}}",
        "body_template": "Hi {{trigger.full_name}},\\n\\nThanks for applying for our {{assumptions.target_role}} role. Based on your background in {{step_score_applicant.outputs.matched_required}}, we'd like to move you to the technical screening stage.\\n\\nPlease reply with your availability over the next 3 business days, and we will send the screening exercise.\\n\\nBest,\\nHiring Team",
        "from_name": "Hiring Team"
      }
    },
    {
      "id": "step_send_fail_email",
      "path": "fail_path",
      "app": "Gmail",
      "event": "Send Email",
      "_comment": "Use a polite rejection email when the candidate does not meet the stack threshold. Keep the wording respectful and avoid exposing the numeric score directly.",
      "config": {
        "to": "{{trigger.email}}",
        "subject": "Update on your application",
        "body_template": "Hi {{trigger.full_name}},\\n\\nThank you for taking the time to apply. After reviewing your background against the current requirements for this role, we will not be moving forward at this stage.\\n\\nWe appreciate your interest and encourage you to apply again if a future role is a closer match.\\n\\nBest,\\nHiring Team",
        "from_name": "Hiring Team"
      }
    }
  ],
  "operating_notes": [
    "Tune the keyword lists before going live so they reflect the exact stack of the open role.",
    "Do not auto-reject on a single mismatch term alone; the score should reflect overall fit, not just one keyword.",
    "Store score and rationale in your ATS or spreadsheet if you want an audit trail for recruiter review.",
    "If you expect high applicant volume, add rate-limit handling or authenticated GitHub API requests."
  ]
}
