{
  "id": "KkVSte6Jl5Ss0cNR",
  "name": "Auto-generate job descriptions from briefing notes with OpenAI and Google Docs",
  "tags": [],
  "nodes": [
    {
      "id": "5dcfac82-9cca-4401-bc98-986fa571d049",
      "name": "Google Drive Trigger",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "position": [
        496,
        368
      ],
      "parameters": {
        "event": "fileCreated",
        "options": {
          "fileType": "application/vnd.google-apps.document"
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_DRIVE_FOLDER_ID",
          "cachedResultUrl": "https://drive.google.com/drive/folders/YOUR_GOOGLE_DRIVE_FOLDER_ID",
          "cachedResultName": "Your_Folder"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "334206ef-b5b5-4258-a5ff-68af37a4212f",
      "name": "Create timestamped subfolder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        720,
        368
      ],
      "parameters": {
        "name": "={{ $json.name }}_{{ $json.createdTime }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_DRIVE_FOLDER_ID",
          "cachedResultUrl": "https://drive.google.com/drive/folders/YOUR_GOOGLE_DRIVE_FOLDER_ID",
          "cachedResultName": "Your_Folder"
        },
        "resource": "folder"
      },
      "typeVersion": 3
    },
    {
      "id": "c4468b02-0860-4320-bcc2-5778312368c5",
      "name": "Move briefing doc to subfolder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        944,
        368
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Google Drive Trigger').item.json.id }}"
        },
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "operation": "move"
      },
      "typeVersion": 3
    },
    {
      "id": "5f15c82a-42d7-4292-92e3-b8378c064717",
      "name": "Read Google Doc content",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        1168,
        368
      ],
      "parameters": {
        "operation": "get",
        "documentURL": "={{ $json.id }}"
      },
      "typeVersion": 2
    },
    {
      "id": "0ba80b55-ae17-4009-ad7c-b5acce3c7e63",
      "name": "Extract job data from transcript",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1392,
        368
      ],
      "parameters": {
        "text": "=ROLE & OBJECTIVE\nYou are an extraction assistant. Analyze a German transcript between a Hiring Manager and a Recruiter and produce a structured Job Description in professional Business English. Rephrase only what is present in the transcript; do not add new facts.\n\nINPUT\nYou will receive raw German transcript text.\n\nSTRICT RULES\n- Extract ONLY from the transcript. Do not invent information.\n- Do NOT copy examples, instructions, or schema descriptions into any field.\n- Translate to professional Business English and elevate phrasing, but stay faithful to the source facts.\n- If an item is missing or not clearly stated:\n  - Strings => null\n  - Arrays => []\n  - Booleans => null if unclear\n- Responsibilities vs. Requirements:\n  - Responsibilities = action-oriented tasks the role performs.\n  - Requirements = skills/experience needed to qualify.\n- Seniority inference:\n  - Only infer if years of experience or responsibility scope clearly justify it (e.g., ownership of strategy, leadership). Otherwise set null.\n- Tech stack:\n  - Extract exact tool names from the transcript. Correct obvious transcription errors (e.g., \"Uipath\" -> \"UiPath\") only if certain; if unsure, omit.\n- Remote policy:\n  - Set is_remote_friendly = true/false only if clearly stated; else null.\n- Output format:\n  - Return ONLY a single JSON object conforming to the structure below.\n  - No Markdown, no code fences, no comments.\n  - Deduplicate array items. Do not include placeholder text like \"TBD\".\n\nOUTPUT JSON STRUCTURE\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"meta_data\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"job_title\": {\n          \"type\": \"string\",\n          \"description\": \"The official title of the position.\"\n        },\n        \"job_subtitle\": {\n          \"type\": \"string\",\n          \"description\": \"Any specific focus mentioned, e.g., 'Focus on HR Processes'.\"\n        },\n        \"department\": {\n          \"type\": \"string\",\n          \"description\": \"The department or team name.\"\n        },\n        \"employment_type\": {\n          \"type\": \"string\",\n          \"description\": \"Full-time, Part-time, etc.\"\n        },\n        \"seniority_level\": {\n          \"type\": \"string\",\n          \"description\": \"Inferred level, e.g., 'Professional', 'Senior', 'Lead'.\"\n        }\n      },\n      \"required\": [\"job_title\", \"department\"]\n    },\n    \"location_details\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"locations\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"List of physical cities or office locations mentioned.\"\n        },\n        \"is_remote_friendly\": {\n          \"type\": \"boolean\",\n          \"description\": \"True if home office, remote work, or 'work from anywhere' is mentioned.\"\n        },\n        \"location_policy_text\": {\n          \"type\": \"string\",\n          \"description\": \"A short sentence describing the policy, e.g., 'Remote working across Germany possible'.\"\n        }\n      },\n      \"required\": [\"locations\", \"is_remote_friendly\"]\n    },\n    \"content_core\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"mission_statement\": {\n          \"type\": \"string\",\n          \"description\": \"A compelling 2-3 sentence pitch explaining why this role exists and its impact.\"\n        },\n        \"responsibilities\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"List of 4-6 key tasks. Use active verbs (e.g., 'Develop', 'Manage', 'Implement').\"\n        },\n        \"tech_stack\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Specific tools, languages, or platforms mentioned (e.g., n8n, UiPath, Python, Workday).\"\n        }\n      },\n      \"required\": [\"mission_statement\", \"responsibilities\"]\n    },\n    \"requirements_profile\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hard_skills\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Professional experience and technical skills required.\"\n        },\n        \"soft_skills\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Personality traits, mindset, or social skills mentioned.\"\n        },\n        \"languages\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Spoken languages required including proficiency level if mentioned.\"\n        }\n      },\n      \"required\": [\"hard_skills\"]\n    },\n    \"benefits_and_culture\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"benefits_list\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Perks offered (e.g., holidays, learning budget, mobility).\"\n        },\n        \"culture_statement\": {\n          \"type\": \"string\",\n          \"description\": \"Statements regarding DE&I, company values, or working atmosphere.\"\n        }\n      }\n    },\n    \"contact_info\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"recruiter_name\": {\n          \"type\": \"string\"\n        },\n        \"recruiter_role\": {\n          \"type\": \"string\"\n        },\n        \"call_to_action\": {\n          \"type\": \"string\",\n          \"description\": \"Closing sentence encouraging application.\"\n        }\n      }\n    }\n  },\n  \"required\": [\n    \"meta_data\",\n    \"location_details\",\n    \"content_core\",\n    \"requirements_profile\"\n  ]\n}\n\nTRANSCRIPT\n{{ $json.content }}\n",
        "options": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "d09466f2-74e4-406e-af64-007f135e822a",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1472,
        592
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.1",
          "cachedResultName": "gpt-5.1"
        },
        "options": {
          "textFormat": {
            "textOptions": {
              "type": "json_schema",
              "schema": "{\n  \"type\": \"object\",\n  \"additionalProperties\": false,\n  \"properties\": {\n    \"meta_data\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"job_title\": {\n          \"type\": [\"string\", \"null\"],\n          \"description\": \"The official title of the position.\"\n        },\n        \"job_subtitle\": {\n          \"type\": [\"string\", \"null\"],\n          \"description\": \"Any specific focus mentioned, e.g., 'Focus on HR Processes'.\"\n        },\n        \"department\": {\n          \"type\": [\"string\", \"null\"],\n          \"description\": \"The department or team name.\"\n        },\n        \"employment_type\": {\n          \"type\": [\"string\", \"null\"],\n          \"description\": \"Full-time, Part-time, etc.\"\n        },\n        \"seniority_level\": {\n          \"type\": [\"string\", \"null\"],\n          \"description\": \"Inferred level, e.g., 'Professional', 'Senior', 'Lead'.\"\n        }\n      },\n      \"required\": [\"job_title\", \"job_subtitle\", \"department\", \"employment_type\", \"seniority_level\"]\n    },\n    \"location_details\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"locations\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"List of physical cities or office locations mentioned.\"\n        },\n        \"is_remote_friendly\": {\n          \"type\": [\"boolean\", \"null\"],\n          \"description\": \"True if home office, remote work, or 'work from anywhere' is mentioned.\"\n        },\n        \"location_policy_text\": {\n          \"type\": [\"string\", \"null\"],\n          \"description\": \"A short sentence describing the policy, e.g., 'Remote working across Germany possible'.\"\n        }\n      },\n      \"required\": [\"locations\", \"is_remote_friendly\", \"location_policy_text\"]\n    },\n    \"content_core\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"mission_statement\": {\n          \"type\": [\"string\", \"null\"],\n          \"description\": \"A compelling 2-3 sentence pitch explaining why this role exists and its impact.\"\n        },\n        \"responsibilities\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"List of 4-6 key tasks. Use active verbs (e.g., 'Develop', 'Manage', 'Implement').\"\n        },\n        \"tech_stack\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Specific tools, languages, or platforms mentioned (e.g., n8n, UiPath, Python, Workday).\"\n        }\n      },\n      \"required\": [\"mission_statement\", \"responsibilities\", \"tech_stack\"]\n    },\n    \"requirements_profile\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"hard_skills\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Professional experience and technical skills required.\"\n        },\n        \"soft_skills\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Personality traits, mindset, or social skills mentioned.\"\n        },\n        \"languages\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Spoken languages required including proficiency level if mentioned.\"\n        }\n      },\n      \"required\": [\"hard_skills\", \"soft_skills\", \"languages\"]\n    },\n    \"benefits_and_culture\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"benefits_list\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Perks offered (e.g., holidays, learning budget, mobility).\"\n        },\n        \"culture_statement\": {\n          \"type\": [\"string\", \"null\"],\n          \"description\": \"Statements regarding DE&I, company values, or working atmosphere.\"\n        }\n      },\n      \"required\": [\"benefits_list\", \"culture_statement\"]\n    },\n    \"contact_info\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"recruiter_name\": {\n          \"type\": [\"string\", \"null\"]\n        },\n        \"recruiter_role\": {\n          \"type\": [\"string\", \"null\"]\n        },\n        \"call_to_action\": {\n          \"type\": [\"string\", \"null\"],\n          \"description\": \"Closing sentence encouraging application.\"\n        }\n      },\n      \"required\": [\"recruiter_name\", \"recruiter_role\", \"call_to_action\"]\n    }\n  },\n  \"required\": [\n    \"meta_data\",\n    \"location_details\",\n    \"content_core\",\n    \"requirements_profile\",\n    \"benefits_and_culture\",\n    \"contact_info\"\n  ]\n}"
            }
          }
        },
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "7ca4d113-efb5-4152-bd49-9dd87366fb1c",
      "name": "Parse AI JSON output",
      "type": "n8n-nodes-base.code",
      "position": [
        1744,
        368
      ],
      "parameters": {
        "jsCode": "const aiOutput = $input.all().map((item) => item.json);\n\nconst parsedOutput = aiOutput\n  .map((output) => {\n    try {\n      return JSON.parse(output.output);\n    } catch (error) {\n      console.log(`Failed to parse output: ${output.output}`);\n      return null;\n    }\n  })\n  .filter(Boolean); // filter out null values\n\nreturn parsedOutput;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "355dc4d1-26e5-4c10-aa92-e4a044c16247",
      "name": "Log job data in Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1968,
        368
      ],
      "parameters": {
        "columns": {
          "value": {
            "job_title": "={{ $json.meta_data.job_title }}",
            "languages": "={{ $json.requirements_profile.languages }}",
            "locations": "={{ $json.location_details.locations }}",
            "department": "={{ $json.meta_data.department }}",
            "tech_stack": "={{ $json.content_core.tech_stack }}",
            "hard_skills": "={{ $json.requirements_profile.hard_skills }}",
            "soft_skills": "={{ $json.requirements_profile.soft_skills }}",
            "contact_info": "={{ $json.contact_info }}",
            "job_subtitle": "={{ $json.meta_data.job_subtitle }}",
            "benefits_list": "={{ $json.benefits_and_culture.benefits_list }}",
            "employment_type": "={{ $json.meta_data.employment_type }}",
            "seniority_level": "={{ $json.meta_data.seniority_level }}",
            "responsibilities": "={{ $json.content_core.responsibilities }}",
            "culture_statement": "={{ $json.benefits_and_culture.culture_statement }}",
            "mission_statement": "={{ $json.content_core.mission_statement }}",
            "is_remote_friendly": "={{ $json.location_details.is_remote_friendly }}",
            "location_policy_text": "={{ $json.location_details.location_policy_text }}"
          },
          "schema": [
            {
              "id": "job_title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "job_title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "job_subtitle",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "job_subtitle",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "department",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "department",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "employment_type",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "employment_type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "seniority_level",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "seniority_level",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "locations",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "locations",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "is_remote_friendly",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "is_remote_friendly",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "location_policy_text",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "location_policy_text",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "mission_statement",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "mission_statement",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "responsibilities",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "responsibilities",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "tech_stack",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "tech_stack",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "hard_skills",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "hard_skills",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "soft_skills",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "soft_skills",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "languages",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "languages",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "benefits_list",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "benefits_list",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "culture_statement",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "culture_statement",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "contact_info",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "contact_info",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEETS_ID/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEETS_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEETS_ID/edit?usp=drivesdk",
          "cachedResultName": "Your_Spreadsheet"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "808116d1-46f7-44b6-abd7-8e08f9700992",
      "name": "OpenAI Chat Model (JD-Writer)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2416,
        480
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.1",
          "cachedResultName": "gpt-5.1"
        },
        "options": {},
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "36d479a6-8dd4-49ee-bd3a-e3f9ebc5067b",
      "name": "Check if JD is approved",
      "type": "n8n-nodes-base.if",
      "position": [
        2992,
        368
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "239cd203-6076-4865-9ce7-f27019b33fbb",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data['Approved?'] }}",
              "rightValue": "yes"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "040f33ce-bb7c-420b-a086-62c22c84201e",
      "name": "Build JD-Writer prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        2192,
        368
      ],
      "parameters": {
        "jsCode": "// Build the full prompt for JD-Writer depending on path (create vs. revise)\n\nconst AGENT_NODE_NAME  = 'JD-Writer';                         \nconst IF_NODE_NAME     = 'Check if JD is approved';                                \nconst TEAMS_NODE_NAME  = 'Send JD to Teams for approval';\n\n\nconst S = v => (v === undefined || v === null ? \"\" : String(v));\nconst getByPath = (obj, path) => {\n  try {\n    let cur = obj;\n    for (const seg of path.split('.')) {\n      const m = seg.match(/^([^\\[]+)(?:\\[(\\d+)\\])?$/); // supports \"choices[0]\"\n      if (!m) return undefined;\n      const key = m[1];\n      const idx = m[2];\n      cur = cur?.[key];\n      if (cur === undefined || cur === null) return undefined;\n      if (idx !== undefined) {\n        if (!Array.isArray(cur)) return undefined;\n        cur = cur[Number(idx)];\n      }\n    }\n    return cur;\n  } catch { return undefined; }\n};\nconst getLastItemFromNode = (nodeName) => {\n  let items = [];\n  try { items = items.concat($items(nodeName, 0) || []); } catch {}\n  try { items = items.concat($items(nodeName, 1) || []); } catch {}\n  if (!items.length) {\n    try { items = $items(nodeName) || []; } catch {}\n  }\n  return items.length ? items[items.length - 1] : null;\n};\n// Deep scan: find the first string that looks like HTML\nconst findHtmlString = (obj) => {\n  const seen = new Set();\n  const looksLikeHtml = (str) => {\n    const s = S(str);\n    return /<\\s*html[\\s>]/i.test(s) || /<\\s*body[\\s>]/i.test(s) || /<\\/(p|div|ul|li|h[1-6])>/i.test(s);\n  };\n  const dfs = (val) => {\n    if (val === null || val === undefined) return null;\n    if (typeof val === 'string') return looksLikeHtml(val) ? val : null;\n    if (typeof val !== 'object') return null;\n    if (seen.has(val)) return null;\n    seen.add(val);\n    if (Array.isArray(val)) {\n      for (const v of val) {\n        const hit = dfs(v);\n        if (hit) return hit;\n      }\n    } else {\n      for (const k of Object.keys(val)) {\n        const hit = dfs(val[k]);\n        if (hit) return hit;\n      }\n    }\n    return null;\n  };\n  return dfs(obj) || \"\";\n};\n\n// ---------- 1) Collect sheet/input data ----------\nconst firstItem = $input.first();\nconst inputData = firstItem?.json ?? {};\nconst data = inputData.data ?? {};\n\nconst FIELDS = [\n  'job_title','job_subtitle','department','employment_type','locations',\n  'is_remote_friendly','location_policy_text','mission_statement','responsibilities',\n  'tech_stack','hard_skills','soft_skills','languages','benefits_list',\n  'culture_statement','contact_info'\n];\n\nconst base = {};\nfor (const f of FIELDS) base[f] = S(getByPath(inputData, f) ?? getByPath(data, f) ?? \"\");\n\n// ---------- 2) Detect feedback / IF path ----------\nconst teamsItem = getLastItemFromNode(TEAMS_NODE_NAME);\n// IF outputs\nlet ifTrueItems = [], ifFalseItems = [];\ntry { ifTrueItems  = $items(IF_NODE_NAME, 0) || []; } catch {}\ntry { ifFalseItems = $items(IF_NODE_NAME, 1) || []; } catch {}\nconst cameFromIfTrue  = Boolean(ifTrueItems.length);\nconst cameFromIfFalse = Boolean(ifFalseItems.length);\n\nconst feedback = S(\n  getByPath(teamsItem?.json, 'feedback') ??\n  getByPath(inputData, 'feedback') ??\n  getByPath(data, 'feedback') ??\n  \"\"\n).trim();\n\n// ---------- 3) Get previous agent output (HTML) ----------\nlet previous_html = \"\";\nconst agentItem = getLastItemFromNode(AGENT_NODE_NAME);\nif (agentItem?.json) {\n  const j = agentItem.json;\n  // Try typical paths first (including array indices)\n  const candidates = [\n    'output.html',\n    'output.job_description_html',\n    'output.response',\n    'output.text',\n    'response',\n    'text',\n    'data.response',\n    'data.choices[0].message.content',\n    'choices[0].message.content',\n    'message.content'\n  ];\n  for (const p of candidates) {\n    const v = getByPath(j, p);\n    if (typeof v === 'string' && /<\\s*html|<\\s*body|<\\/(p|div|ul|li|h[1-6])>/i.test(v)) {\n      previous_html = v;\n      break;\n    }\n  }\n  // Fallback: deep scan\n  if (!previous_html) previous_html = findHtmlString(j);\n}\n\n// ---------- 4) Determine mode ----------\nlet mode = cameFromIfFalse ? 'revise' : 'create';\n// Safety: if revise requested but no previous_html available (e.g. first run), fall back to create\nif (mode === 'revise' && !previous_html) mode = 'create';\n\n// ---------- 5) Prompt builder ----------\n// Original prompt template for CREATE mode.\n// Placeholders like {{ $json.job_title }} are substituted.\nconst CREATE_PROMPT_TEMPLATE = `\nYour task:  \nUsing the structured input below, write a **complete, polished job description in English** in the style of a modern, mission-driven employer (similar to innovative EdTech / tech-forward companies).\n\n### Overall style and tone\n\n- Professional, clear, and confident.\n- Positive and mission-driven (without becoming cheesy or overhyped).\n- Inclusive and welcoming; avoid jargon where not needed.\n- Use active voice, strong verbs, and concise sentences.\n- Write for experienced professionals (mid to senior), but keep it approachable.\n- No emojis, no hashtags.\n\n### Structure & content\n\nFollow this structure (using headings in ALL CAPS):\n\n1. **Mission hook & role introduction (1\u20132 short paragraphs)**  \n   - Start with a short, inspiring mission statement using or adapting:\n     - {{ $json.mission_statement }}\n   - Immediately introduce the role with job title, department, employment type, and location context, similar to:  \n     - \u201cStrengthen our {{ $json.department }} Team {{ $json.employment_type }} as a {{ $json.job_title }} {{ $json.job_subtitle }} at our locations in {{ $json.locations }}{{ $json.is_remote_friendly }}.\u201d\n   - If available, briefly mention the location/remote policy in a natural way:\n     - Use {{ $json.location_policy_text }} appropriately.\n   - Keep this section crisp and inviting.\n\n2. **Short role overview (1 paragraph)**  \n   - Summarize what the role focuses on, in 2\u20134 sentences:\n     - Use the themes from {{ $json.responsibilities }}, {{ $json.tech_stack }}, {{ $json.department }}.\n   - Emphasize impact, cross-functional collaboration, and key topics (e.g., digital transformation, AI, automation, etc. if relevant from input).\n   - Do not list responsibilities here; this is a narrative overview.\n\n3. **YOUR RESPONSIBILITIES (bullet list)**  \n   - Add the heading: \\`YOUR RESPONSIBILITIES\\`\n   - Convert {{ $json.responsibilities }} into **clear, action-oriented bullet points**.\n   - Each bullet:\n     - Starts with a strong verb in present tense (e.g., \u201cLead\u2026\u201d, \u201cDesign\u2026\u201d, \u201cImplement\u2026\u201d, \u201cCollaborate\u2026\u201d).\n     - Focuses on one main idea; avoid overly long multi-clause bullets.\n   - If {{ $json.tech_stack }} is relevant, integrate tools/technologies naturally into the bullets (e.g., \u201cusing tools such as \u2026\u201d).\n   - Aim for ~5\u20138 bullets where possible (depending on input length/quality).\n\n4. **YOUR PROFILE (bullet list)**  \n   - Add the heading: \\`YOUR PROFILE\\`\n   - Combine and rephrase:\n     - {{ $json.hard_skills }}\n     - {{ $json.soft_skills }}\n     - {{ $json.languages }}\n   - Turn them into candidate-focused bullet points (e.g., \u201cSeveral years of experience in\u2026\u201d, \u201cHands-on expertise with\u2026\u201d, \u201cYou enjoy\u2026\u201d, \u201cYou communicate fluently in\u2026\u201d).\n   - Group related items logically (e.g., experience, tools, mindset, languages).\n   - Keep it realistic and not excessively long (again ~5\u20138 bullets when possible).\n\n5. **WE OFFER (bullet list)**  \n   - Add the heading: \\`WE OFFER\\`\n   - Use {{ $json.benefits_list }} and turn it into attractive but concrete bullet points.\n   - Mix:\n     - Work model & flexibility (remote, hybrid, working hours)\n     - Learning & development opportunities\n     - Culture/values-related benefits\n     - Financial/transport/perks (e.g. commute support, bonuses, etc.)\n   - Avoid duplicating the culture section; keep this benefits-focused.\n\n6. **Company & culture paragraph**  \n   - Using {{ $json.culture_statement }} and {{ $json.mission_statement }}, write 1\u20132 short paragraphs that:\n     - Briefly describe what the organization does (industry, size or positioning if given).\n     - Highlight being tech-forward / data-driven / innovative **only if** implied by the inputs.\n     - Emphasize inclusive culture and collaboration.\n   - Keep it factual and not overly long.\n\n7. **Call to action & application information**  \n   - Encourage candidates to apply in a friendly, straightforward sentence or two.\n   - If {{ $json.contact_info }} is provided:\n     - Mention how to apply or who to contact using this information in a natural way (no raw JSON or labels).\n   - If no contact info is available, use a generic call to apply via the careers page.\n\n8. **Diversity & inclusion statement (short, clear)**  \n   - End with a concise, inclusive statement in line with modern standards, inspired by but not copying typical texts.\n   - Emphasize that all qualified applicants are welcome regardless of background, and that the company values diversity and an inclusive culture.\n   - Keep it to 2\u20134 sentences, not a long legal block.\n\n### Formatting rules\n\n- **Output must be valid HTML.**\n- Wrap the entire job description in semantic HTML tags (e.g. use <html>, <body>, and appropriate sections or <div> containers).\n- Use headings in ALL CAPS as text inside heading tags (e.g. <h2>YOUR RESPONSIBILITIES</h2>).\n- Use <p> tags for paragraphs and <ul><li>...</li></ul> for bullet lists.\n- Do **not** use Markdown.\n- Do **not** mention the JSON fields, variables, or any technical prompt details in the output.\n- Do **not** quote the input verbatim if it\u2019s awkward; you may lightly edit/standardize for style and grammar.\n- If certain inputs are missing or empty, **gracefully omit** the related parts and still produce a coherent, polished job description.\n- Do **not** add any explanations, intros, or meta-comments before or after the HTML. The output must be the HTML job description only.\n\nInput data to use\n\n- Job title: {{ $json.job_title }}\n- Job subtitle: {{ $json.job_subtitle }}\n- Department / team: {{ $json.department }}\n- Employment type: {{ $json.employment_type }}\n- Locations: {{ $json.locations }}\n- Remote-friendliness: {{ $json.is_remote_friendly }}\n- Location policy text: {{ $json.location_policy_text }}\n- Mission statement: {{ $json.mission_statement }}\n- Responsibilities: {{ $json.responsibilities }}\n- Tech stack: {{ $json.tech_stack }}\n- Hard skills: {{ $json.hard_skills }}\n- Soft skills: {{ $json.soft_skills }}\n- Languages: {{ $json.languages }}\n- Benefits list: {{ $json.benefits_list }}\n- Culture statement: {{ $json.culture_statement }}\n- Contact info / application details: {{ $json.contact_info }}\n`.trim();\n\n// Placeholder substitution: {{ $json.field }} -> actual value from sheet\nconst fillCreateTemplate = (tpl, ctx) =>\n  tpl.replace(/\\{\\{\\s*\\$json\\.([a-zA-Z0-9_]+)\\s*\\}\\}/g, (_, key) => S(ctx[key]).trim());\n\n// Revise prompts\nconst REVISE_SYSTEM = `\nYou are an expert HR copywriter. You will revise an existing  job description based on feedback.\nRules:\n- Apply the feedback precisely and minimally; do not rewrite from scratch.\n- Preserve structure, section order, headings, and any content not mentioned in the feedback.\n- Keep the tone professional, clear, modern, and inclusive.\n- Output must be valid HTML only (no explanations).\n`.trim();\n\nconst makeReviseUser = (prevHtml, fb) => `\nFeedback:\n${fb || '(no additional feedback provided)'}\n\nPrevious draft HTML:\n${prevHtml}\n\nInstructions:\n- Update only the necessary parts to address the feedback.\n- Keep HTML valid; keep headings in ALL CAPS as they are.\n- Do not insert new sections unless explicitly requested by the feedback.\n`.trim();\n\n// ---------- 6) Assemble system/user messages ----------\nlet system, user;\nif (mode === 'create') {\n  system = `You are an expert HR copywriter specialized in creating clear, engaging, and modern job descriptions for tech- and transformation-focused roles.`;\n  user   = fillCreateTemplate(CREATE_PROMPT_TEMPLATE, base);\n} else {\n  system = REVISE_SYSTEM;\n  user   = makeReviseUser(previous_html, feedback);\n}\n\n// ---------- 7) Return ----------\nreturn [{\n  json: {\n    mode,\n    system,\n    user,\n    feedback,\n    previous_html,\n    // Useful for debugging in the n8n UI:\n    _debug: {\n      cameFromIfTrue,\n      cameFromIfFalse,\n      agentItemKeys: agentItem?.json ? Object.keys(agentItem.json) : [],\n      foundHtml: Boolean(previous_html)\n    },\n    // Pass sheet data downstream if needed\n    ...base\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "177ea090-e232-479f-bb8b-00e692970807",
      "name": "JD-Writer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2416,
        240
      ],
      "parameters": {
        "text": "={{ $json.user }}\n\n",
        "options": {
          "systemMessage": "={{ $json.system }}"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "eac6fe8f-0c99-434a-942c-e10ffacb3868",
      "name": "Send JD to Teams for approval",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        2768,
        240
      ],
      "parameters": {
        "chatId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_MS_TEAMS_CHAT_ID",
          "cachedResultUrl": "https://teams.microsoft.com/l/chat/YOUR_CHAT_URL",
          "cachedResultName": "Your_Teams_Chat"
        },
        "message": "={{ $json.output }}",
        "options": {},
        "resource": "chatMessage",
        "operation": "sendAndWait",
        "formFields": {
          "values": [
            {
              "fieldType": "dropdown",
              "fieldLabel": "Approved?",
              "defaultValue": "yes",
              "fieldOptions": {
                "values": [
                  {
                    "option": "yes"
                  },
                  {
                    "option": "no"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "feedback",
              "placeholder": "what should i change or improve?"
            }
          ]
        },
        "responseType": "customForm"
      },
      "typeVersion": 2
    },
    {
      "id": "fb85dea3-ae4a-42e6-9c55-ad89c0af4fa8",
      "name": "Convert approved JD to PDF",
      "type": "n8n-nodes-htmlcsstopdf.htmlcsstopdf",
      "position": [
        3200,
        272
      ],
      "parameters": {
        "html_content": "={{ $('JD-Writer').item.json.output }}",
        "output_format": "file"
      },
      "typeVersion": 1
    },
    {
      "id": "4177edba-cb3e-485d-89c2-9a5a2b29e08e",
      "name": "Upload PDF to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        3408,
        272
      ],
      "parameters": {
        "name": "={{ $('Append row in sheet').item.json.job_title }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create folder').item.json.id }}"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "830be834-6ba7-4170-ae21-2bd2462078fd",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        0
      ],
      "parameters": {
        "width": 520,
        "height": 900,
        "content": "## Auto-generate job descriptions from briefing notes\n\nTurn a **Google Docs role briefing transcript** (e.g. from a Hiring Manager / Recruiter conversation) into a polished, structured **job description** in HTML and PDF -- fully automated with AI and a human-in-the-loop approval via Microsoft Teams.\n\n## How it works\n\n1. A new Google Doc appears in a watched Drive folder (the briefing transcript).\n2. The doc is moved into a timestamped subfolder for organization.\n3. An AI Agent (OpenAI) reads the transcript and extracts structured job data (title, responsibilities, skills, benefits, etc.) into JSON.\n4. The extracted data is logged in a Google Sheets tracker.\n5. A second AI Agent (JD-Writer) generates a full HTML job description from the structured data.\n6. The draft is sent to **Microsoft Teams** for review with an approve/reject form.\n7. If approved: the HTML is converted to PDF and uploaded to Google Drive.\n8. If rejected: feedback is looped back and the JD is revised automatically.\n\n## Setup steps\n\n1. **Google Drive & Docs** -- Create OAuth2 credentials. Set the watched folder ID in the Google Drive Trigger node.\n2. **Google Sheets** -- Create a spreadsheet with columns matching the job data schema (job_title, department, responsibilities, etc.). Update the Sheet ID.\n3. **OpenAI** -- Add your API key. Used for both the data extraction agent and the JD-Writer agent.\n4. **Microsoft Teams** -- Create OAuth2 credentials. Set the Teams chat ID in the approval node.\n5. **HTML-to-PDF** -- Install the community node `n8n-nodes-htmlcsstopdf` (self-hosted only). Add the API credential.\n\n> **Community node required:** `n8n-nodes-htmlcsstopdf` -- this template works on **self-hosted n8n only**."
      },
      "typeVersion": 1
    },
    {
      "id": "9a6d4027-7942-4a83-849e-266549729aef",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 360,
        "height": 130,
        "content": "### Step 1: Trigger & File Organization\nGoogle Drive Trigger watches a folder for new Google Docs. When detected, a timestamped subfolder is created and the document is moved into it."
      },
      "typeVersion": 1
    },
    {
      "id": "bec98195-060c-4ec7-92a5-4a3fadaa4c9a",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1264,
        176
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 140,
        "content": "### Step 2: AI Data Extraction\nThe Google Doc content is read and passed to an OpenAI AI Agent that extracts structured job data (title, department, responsibilities, skills, benefits, etc.) from the German transcript into a JSON schema."
      },
      "typeVersion": 1
    },
    {
      "id": "3dab8a4e-6d68-4e62-aa13-65d05ffdda56",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2352,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "content": "### Step 3: JD Generation & Approval Loop\nExtracted data is logged in Google Sheets, then an Input Builder prepares a prompt for the JD-Writer AI Agent. The generated HTML job description is sent to Microsoft Teams for review. If rejected, feedback is looped back for revision."
      },
      "typeVersion": 1
    },
    {
      "id": "563eb0c3-c67d-49af-ba03-32d7bb1e8902",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3184,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 360,
        "height": 130,
        "content": "### Step 4: PDF Export & Upload\nOnce approved, the HTML job description is converted to PDF via HTML-to-PDF API and uploaded to the Google Drive subfolder alongside the original briefing document."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "cd128e71-7267-4241-8e79-babf37fa4df6",
  "connections": {
    "JD-Writer": {
      "main": [
        [
          {
            "node": "Send JD to Teams for approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Extract job data from transcript",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive Trigger": {
      "main": [
        [
          {
            "node": "Create timestamped subfolder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI JSON output": {
      "main": [
        [
          {
            "node": "Log job data in Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build JD-Writer prompt": {
      "main": [
        [
          {
            "node": "JD-Writer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if JD is approved": {
      "main": [
        [
          {
            "node": "Convert approved JD to PDF",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build JD-Writer prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Google Doc content": {
      "main": [
        [
          {
            "node": "Extract job data from transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert approved JD to PDF": {
      "main": [
        [
          {
            "node": "Upload PDF to Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create timestamped subfolder": {
      "main": [
        [
          {
            "node": "Move briefing doc to subfolder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log job data in Google Sheets": {
      "main": [
        [
          {
            "node": "Build JD-Writer prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model (JD-Writer)": {
      "ai_languageModel": [
        [
          {
            "node": "JD-Writer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Send JD to Teams for approval": {
      "main": [
        [
          {
            "node": "Check if JD is approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move briefing doc to subfolder": {
      "main": [
        [
          {
            "node": "Read Google Doc content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract job data from transcript": {
      "main": [
        [
          {
            "node": "Parse AI JSON output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}