{
  "name": "Nocodework — auto-feedback dla kandydatow",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "nocodework-candidate-feedback",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "wh-cand-1",
      "name": "Zgloszenie kandydata",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [0, 0]
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "typeValidation": "strict", "version": 2 },
          "combinator": "and",
          "conditions": [
            {
              "id": "secret-check",
              "leftValue": "={{ $json.headers['x-form-secret'] }}",
              "rightValue": "REPLACE_WITH_FORM_SECRET",
              "operator": { "type": "string", "operation": "equals" }
            }
          ]
        },
        "options": {}
      },
      "id": "if-secret-1",
      "name": "Sprawdz sekret",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [220, 0]
    },
    {
      "parameters": {
        "jsCode": "// Buduje prompt oceny. Prompt trzymany tu, a nie w HTTP node — latwiej go czytac i zmieniac.\n// Progi i zakazane slowa sa czescia promptu, bo to one decyduja o jakosci feedbacku.\n\nconst b = $input.first().json.body || {};\n\nconst kandydat = {\n  imie: b.imie || b.name || 'Kandydat',\n  email: b.email || '',\n  telefon: b.telefon || b.phone || '',\n  doswiadczenie: b.doswiadczenie || b.experience || '',\n  narzedzia: b.narzedzia || b.tools || '',\n  klient_b2b: b.klient_b2b || b.b2b || '',\n  motywacja: b.motywacja || b.motivation || ''\n};\n\nconst systemPrompt = [\n  'Jestes asystentem rekrutacyjnym Nocodework (automatyzacja Make/n8n + AI dla firm 5-250 osob).',\n  'Oceniasz kandydata na stanowisko Automation Specialist.',\n  'Wymagania: Make lub n8n produkcyjnie, praca z API, AI/LLM, doswiadczenie z klientem B2B, odpowiedzialnosc za projekt.',\n  'Firma jawnie mowi: brak czesci wymagan technicznych NIE dyskwalifikuje, jesli jest doswiadczenie z klientem B2B i postawa.',\n  '',\n  'PROGI: 75+ = invite. Ponizej 75 = reject.',\n  '',\n  'JAK PISAC feedback_pl:',\n  '- Zwracaj sie BEZPOSREDNIO do kandydata na \"Ty\". Zacznij od jego imienia.',\n  '- Cytuj KONKRETY z jego zgloszenia: nazwy narzedzi, liczby, lata. Bez tego feedback jest bezwartosciowy.',\n  '- Zdania krotkie, max 15 slow.',\n  '- ZAKAZANE slowa: potencjal, atut, rozwoj, pasja, dynamiczny, kompleksowy, cenny, doceniamy, zachecamy, imponujace.',\n  '- Przy odmowie: powiedz WPROST czego zabraklo i co konkretnie zrobic, zeby za rok wyszlo.',\n  '- Nie chwal na sile. Badz uczciwy, ale nie okrutny.',\n  '',\n  'PRZYKLAD ZLEGO feedbacku (NIGDY tak nie pisz):',\n  '\"Doceniamy Twoje doswiadczenie i potencjal. Zachecamy do dalszego rozwoju. Twoja pasja jest cenna.\"',\n  '',\n  'PRZYKLAD DOBREGO (zaproszenie):',\n  '\"Marek, 40 workflow n8n na wlasnym VPS to dokladnie to, czego szukamy. Sam prowadzisz discovery i wyceny - u nas robi sie tak samo. Chcemy pogadac o tych 12 klientach: jak dzielisz utrzymanie miedzy nich.\"',\n  '',\n  'PRZYKLAD DOBREGO (odmowa):',\n  '\"Anna, Zapier i integracja z Airtable to dobry start, ale szukamy kogos z wdrozeniami produkcyjnymi u klienta. Twoje automatyzacje dzialaly wewnatrz agencji - to inna skala odpowiedzialnosci. Zrob 2-3 realne wdrozenia w Make lub n8n dla zewnetrznego klienta i wroc do nas.\"',\n  '',\n  'Zwroc WYLACZNIE JSON: {\"score\": 0-100, \"strengths\": [3 konkretne], \"gap\": \"jedna realna luka\", \"recommendation\": \"invite\" lub \"reject\", \"feedback_pl\": \"3-4 zdania\"}'\n].join('\\n');\n\nconst userPrompt = [\n  'Kandydat: ' + kandydat.imie,\n  'Doswiadczenie: ' + kandydat.doswiadczenie,\n  'Narzedzia: ' + kandydat.narzedzia,\n  'Praca z klientem B2B: ' + kandydat.klient_b2b,\n  'Motywacja: ' + kandydat.motywacja\n].join('\\n');\n\nreturn [{ json: { kandydat, systemPrompt, userPrompt } }];"
      },
      "id": "code-prompt-1",
      "name": "Zbuduj prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [440, -80]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  model: 'llama-3.3-70b-versatile',\n  temperature: 0.4,\n  response_format: { type: 'json_object' },\n  messages: [\n    { role: 'system', content: $json.systemPrompt },\n    { role: 'user', content: $json.userPrompt }\n  ]\n}) }}",
        "options": { "timeout": 30000 }
      },
      "id": "groq-score-1",
      "name": "Groq: ocena kandydata",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [660, -80],
      "credentials": {
        "httpHeaderAuth": { "id": "REPLACE_CREDENTIAL_ID", "name": "Groq — Authorization: Bearer gsk_..." }
      }
    },
    {
      "parameters": {
        "jsCode": "// Parsuje odpowiedz LLM. Jesli model zwroci smiec — nie wysylamy nic i oznaczamy do recznej obslugi.\n// Lepiej zeby kandydat nie dostal maila, niz zeby dostal belkot.\n\nconst kandydat = $('Zbuduj prompt').first().json.kandydat;\nlet ocena;\n\ntry {\n  const raw = $input.first().json.choices[0].message.content;\n  ocena = JSON.parse(raw);\n} catch (e) {\n  return [{ json: { kandydat, ok: false, powod: 'Nie udalo sie sparsowac odpowiedzi LLM: ' + e.message } }];\n}\n\nconst wymagane = ['score', 'recommendation', 'feedback_pl'];\nconst brakuje = wymagane.filter(k => ocena[k] === undefined || ocena[k] === null || ocena[k] === '');\nif (brakuje.length) {\n  return [{ json: { kandydat, ok: false, powod: 'Brak pol w odpowiedzi LLM: ' + brakuje.join(', ') } }];\n}\n\nconst zaproszenie = ocena.recommendation === 'invite';\n\nreturn [{ json: { kandydat, ocena, zaproszenie, ok: true } }];"
      },
      "id": "code-parse-1",
      "name": "Parsuj ocene",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [880, -80]
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "typeValidation": "strict", "version": 2 },
          "combinator": "and",
          "conditions": [
            {
              "id": "ok-and-invite",
              "leftValue": "={{ $json.ok && $json.zaproszenie }}",
              "rightValue": true,
              "operator": { "type": "boolean", "operation": "true", "singleValue": true }
            }
          ]
        },
        "options": {}
      },
      "id": "if-invite-1",
      "name": "Zaproszenie?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [1100, -80]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.resend.com/emails",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  from: 'Nocodework <rekrutacja@DOMENA.pl>',\n  to: [$json.kandydat.email],\n  subject: 'Twoje zgloszenie — Automation Specialist',\n  text: $json.ocena.feedback_pl\n    + '\\n\\n---\\nOdezwiemy sie z terminem rozmowy.\\nZespol Nocodework'\n}) }}",
        "options": {}
      },
      "id": "resend-invite-1",
      "name": "Resend: zaproszenie",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1320, -180],
      "credentials": {
        "httpHeaderAuth": { "id": "REPLACE_CREDENTIAL_ID", "name": "Resend — Authorization: Bearer re_..." }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.resend.com/emails",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  from: 'Nocodework <rekrutacja@DOMENA.pl>',\n  to: [$json.kandydat.email],\n  subject: 'Twoje zgloszenie — Automation Specialist',\n  text: $json.ocena.feedback_pl\n    + '\\n\\n---\\nDzieki za czas, ktory poswieciles na zgloszenie.\\nZespol Nocodework'\n}) }}",
        "options": {}
      },
      "id": "resend-reject-1",
      "name": "Resend: odmowa",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1320, 20],
      "credentials": {
        "httpHeaderAuth": { "id": "REPLACE_CREDENTIAL_ID", "name": "Resend — Authorization: Bearer re_..." }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, score: $json.ocena ? $json.ocena.score : null, recommendation: $json.ocena ? $json.ocena.recommendation : null }) }}",
        "options": {}
      },
      "id": "respond-ok-1",
      "name": "Odpowiedz OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [1540, -80]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: false, error: 'forbidden' }) }}",
        "options": { "responseCode": 403 }
      },
      "id": "respond-403-1",
      "name": "Odpowiedz 403",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [440, 140]
    }
  ],
  "connections": {
    "Zgloszenie kandydata": { "main": [[{ "node": "Sprawdz sekret", "type": "main", "index": 0 }]] },
    "Sprawdz sekret": {
      "main": [
        [{ "node": "Zbuduj prompt", "type": "main", "index": 0 }],
        [{ "node": "Odpowiedz 403", "type": "main", "index": 0 }]
      ]
    },
    "Zbuduj prompt": { "main": [[{ "node": "Groq: ocena kandydata", "type": "main", "index": 0 }]] },
    "Groq: ocena kandydata": { "main": [[{ "node": "Parsuj ocene", "type": "main", "index": 0 }]] },
    "Parsuj ocene": { "main": [[{ "node": "Zaproszenie?", "type": "main", "index": 0 }]] },
    "Zaproszenie?": {
      "main": [
        [{ "node": "Resend: zaproszenie", "type": "main", "index": 0 }],
        [{ "node": "Resend: odmowa", "type": "main", "index": 0 }]
      ]
    },
    "Resend: zaproszenie": { "main": [[{ "node": "Odpowiedz OK", "type": "main", "index": 0 }]] },
    "Resend: odmowa": { "main": [[{ "node": "Odpowiedz OK", "type": "main", "index": 0 }]] }
  },
  "settings": { "executionOrder": "v1" }
}
